Skip to content

Commit

Permalink
Adapt for variant to variant relations
Browse files Browse the repository at this point in the history
  • Loading branch information
virajrch committed Mar 19, 2024
1 parent e160c65 commit e8c6f87
Show file tree
Hide file tree
Showing 5 changed files with 193 additions and 7 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module Spree::Admin::ProductsControllerDecorator
def related
load_resource
@relation_types = Spree::Product.relation_types
@relation_types = Spree::RelationType.where(applies_to: ['Spree::Product', 'Spree::Variant']).order(:name) #Spree::Product.relation_types
end
end

Expand Down
16 changes: 13 additions & 3 deletions app/controllers/spree/admin/relations_controller.rb
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
module Spree
module Admin
class RelationsController < BaseController
before_action :load_data, only: [:create, :destroy]
before_action :load_data, only: [:create] # [:create, :destroy]

respond_to :js, :html

def create
@relation = Relation.new(relation_params)
@relation.relatable = @product
@relation.related_to = Spree::Variant.find(relation_params[:related_to_id]).product
if @relation_type.applies_to == "Spree::Product"
@relation.relatable = @product
@relation.related_to = Spree::Variant.find(relation_params[:related_to_id]).product
else
@relation.relatable = @variant
@relation.related_to = Spree::Variant.find(relation_params[:related_to_id])
end
@relation.related_to_type = @relation_type.applies_to
@relation.relatable_type = @relation_type.applies_to
@relation.save

respond_with(@relation)
Expand Down Expand Up @@ -62,6 +69,7 @@ def permitted_attributes
:relation_type,
:relatable,
:related_to_id,
:relatable_id,
:discount_amount,
:relation_type_id,
:related_to_type,
Expand All @@ -72,6 +80,8 @@ def permitted_attributes

def load_data
@product = Spree::Product.friendly.find(params[:product_id])
@relation_type = Spree::RelationType.find(relation_params[:relation_type_id].to_i)
@variant = Spree::Variant.find(relation_params[:relatable_id].to_i)
end

def model_class
Expand Down
102 changes: 102 additions & 0 deletions app/models/spree/variant_decorator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# frozen_string_literal: true

module Spree
module VariantDecorator
def self.prepended(base)
base.has_many :relations, -> { order(:position) }, class_name: 'Spree::Relation', as: :relatable
base.has_many :relation_types, -> { unscope(:order).distinct }, class_name: 'Spree::RelationType', through: :relations

# When a Spree::Product is destroyed, we also want to destroy all
# Spree::Relations "from" it as well as "to" it.
base.after_destroy :destroy_product_relations
base.extend ClassMethods
end

module ClassMethods
# Returns all the Spree::RelationType's which apply_to this class.
def relation_types
Spree::RelationType.where(applies_to: to_s).order(:name)
end

def relation_filter
joins(:product).where('spree_products.deleted_at' => nil)
.where('spree_products.available_on IS NOT NULL')
.where('spree_products.status' => :active)
.references(self)
end
end

# Decides if there is a relevant Spree::RelationType related to this class
# which should be returned for this method.
#
# If so, it calls relations_for_relation_type. Otherwise it passes
# it up the inheritance chain.
# def method_missing(method, *args)
# relation_type = find_relation_type(method)
# if relation_type.nil?
# super
# else
# relations_for_relation_type(relation_type)
# end
# end

def has_related_products?(relation_method)
find_relation_type(relation_method).present?
end

def destroy_product_relations
# First we destroy relationships "from" this Product to others.
relations.destroy_all
# Next we destroy relationships "to" this Product.
Spree::Relation.where(related_to_type: self.class.to_s).where(related_to_id: id).destroy_all
end

private

def find_relation_type(relation_name)
self.class.relation_types.detect do |rt|
format_name(rt.name) == format_name(relation_name)
end
rescue ActiveRecord::StatementInvalid
# This exception is throw if the relation_types table does not exist.
# And this method is getting invoked during the execution of a migration
# from another extension when both are used in a project.
nil
end

# Returns all the Products that are related to this record for the given RelationType.
#
# Uses the Relations to find all the related items, and then filters
# them using +Product.relation_filter+ to remove unwanted items.
def relations_for_relation_type(relation_type)
# Find all the relations that belong to us for this RelationType, ordered by position
related_ids = relations.where(relation_type_id: relation_type.id)
.order(:position)
.select(:related_to_id)

# Construct a query for all these records
result = self.class.where(id: related_ids)

# Merge in the relation_filter if it's available
result = result.merge(self.class.relation_filter) if relation_filter

# make sure results are in same order as related_ids array (position order)
result.where(id: related_ids).order(:position) if result.present?

result
end

# Simple accessor for the class-level relation_filter.
# Could feasibly be overloaded to filter results relative to this
# record (eg. only higher priced items)
def relation_filter
self.class.relation_filter
end

def format_name(name)
name.to_s.downcase.tr(' ', '_').pluralize
end
end
end

::Spree::Variant.prepend(Spree::VariantDecorator)
66 changes: 66 additions & 0 deletions app/views/spree/admin/products/_related_variants_table.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<% variants.each do |variant| %>
<% if variant.relations.any? %>
<table class="table sortable" data-hook="products_table" data-sortable-link="<%= update_positions_admin_product_relations_url(variant.product) %>">
<caption><%= "Relations for Variant SKU: #{variant.sku} Option: #{variant.options_text}" %></caption>
<colgroup>
<col style="width: 3%" />
<col style="width: 7%" />
<col style="width: 8%" />
<col style="width: 35%" />
<col style="width: 15%" />
<col style="width: 10%" />
<col style="width: 20%" />
<col style="width: 2%" />
</colgroup>
<thead>
<tr data-hook="products_header">
<th></th>
<th>SKU</th>
<th>Related To Type</th>
<th><%= Spree.t(:name) %></th>
<th><%= Spree.t(:option) %></th>
<th><%= Spree.t(:type) %></th>
<th><%= Spree.t(:quantity) %>%></th>
<th class="actions"></th>
</tr>
</thead>
<tbody>
<% variant.relations.each do |relation| %>
<tr id="<%= spree_dom_id relation %>" data-hook="products_row">
<td class="handle move-handle">
<% if Spree.version.to_d >= 4.2 && defined?(svg_icon) %>
<%= svg_icon name: "sort.svg", width: '18', height: '18' %>
<% else %>
<span class="icon icon-move handle"></span>
<% end %>
</td>
<td><%= relation.related_to.sku %></td>
<td><%= relation.related_to_type %></td>
<td><%= relation.related_to.name %></td>
<td><%= relation.related_to.try(:options_text) %></td>
<%# if defined? Spree::Frontend %>
<%# binding.break %>
<!-- <td><%#= link_to relation.related_to.name, relation.related_to %></td> -->
<%# else %>
<!-- <td><%#= link_to relation.related_to.name, admin_product_path(relation.related_to) %></td> -->
<%# end %>
<td><%= relation.relation_type.name %></td>
<td>
<%= form_for relation, url: admin_variant_relation_path(relation.relatable, relation) do |f| %>
<div class="input-group justify-content-center">
<%= f.text_field :quantity, class: 'form-control text-center my-1 w-20' %>
<span class="input-group-btn">
<%= f.button Spree.t(:update), type: 'submit', class: 'btn btn-primary m-1' %>
</span>
</div>
<% end %>
</td>
<td class="actions">
<%= link_to_delete relation, url: admin_variant_relation_url(relation.relatable, relation), no_text: true %>
</td>
</tr>
<% end %>
</tbody>
</table>
<% end %>
<% end %>
14 changes: 11 additions & 3 deletions app/views/spree/admin/products/related.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,13 @@
<fieldset>
<legend><%= Spree.t(:add_related_product) %></legend>
<div class="row">
<div id="related_product_name" class="col-md-12 col-xl-5">
<div id="relatable_variant" class="col-md-12 col-xl-3">
<div class="form-group">
<%= label_tag :add_relatable_variant_name, Spree.t(:select_variant) %>
<%= select_tag :add_relatable_variant_name, options_for_select(@product.variants_including_master.map { |v| [(v.options_text.present? ? v.options_text : v.name), v.id] }), class: 'select2' %>
</div>
</div>
<div id="related_product_name" class="col-md-12 col-xl-4">
<div class="form-group">
<%= label_tag :add_variant_name, Spree.t(:name_or_sku_short) %>
<% if Spree.version.to_d >= 4.2 %>
Expand All @@ -34,7 +40,7 @@
<% end %>
</div>
</div>
<div id="related_product_type" class="col-md-7 col-xl-4">
<div id="related_product_type" class="col-md-7 col-xl-2">
<div class="form-group">
<%= label_tag :add_type, Spree.t(:type) %>
<%= select_tag :add_type, options_for_select(@relation_types.map { |rt| [rt.name, rt.id] }), class: 'select2' %>
Expand Down Expand Up @@ -63,6 +69,8 @@

<div id="products-table-wrapper">
<%= render 'related_products_table', product: @product %>
<hr>
<%= render 'related_variants_table', variants: @product.variants_including_master %>
</div>

<%= content_for :head do %>
Expand All @@ -77,8 +85,8 @@
url: this.href,
type: 'POST',
data: {
'relation[related_to_type]' : 'Product',
'relation[related_to_id]': $('#add_variant_name').val(),
'relation[relatable_id]': $('#add_relatable_variant_name').val(),
'relation[relation_type_id]': $('#add_type').val(),
'relation[discount_amount]' : $('#add_discount').val(),
'relation[quantity]' : $('#add_quantity').val()
Expand Down

0 comments on commit e8c6f87

Please sign in to comment.