From d04fca27980b9f39bad8e2be1a9f4ee58db61d75 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 9 Oct 2024 08:23:57 +0200 Subject: [PATCH 1/2] Introduce concept of template and non_template variants All products have variants, but not all variants can actually be sold. When a product has option types, the sellable variants are those that are assigned to option values, whereas the "master" variant is only there as a template for other variants to be created. I'm using the language "template variant" because "master variant" because it more closely matches what the `is_master` flag on the Spree::Variant actually does. --- core/app/models/spree/variant.rb | 5 +++++ core/spec/models/spree/variant_spec.rb | 22 ++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/core/app/models/spree/variant.rb b/core/app/models/spree/variant.rb index 1c20b3f7932..b1de1756a60 100644 --- a/core/app/models/spree/variant.rb +++ b/core/app/models/spree/variant.rb @@ -80,6 +80,11 @@ class Variant < Spree::Base after_destroy :destroy_option_values_variants + scope :template_variants, -> do + left_joins(product: { option_types: :option_values }).where(is_master: true).where.not(spree_option_values: { id: nil }).reorder(nil).distinct + end + scope :non_template_variants, -> { where.not(id: template_variants) } + # Returns variants that are in stock. When stock locations are provided as # a parameter, the scope is limited to variants that are in stock in the # provided stock locations. diff --git a/core/spec/models/spree/variant_spec.rb b/core/spec/models/spree/variant_spec.rb index ee8a3f58290..b24e8b413f6 100644 --- a/core/spec/models/spree/variant_spec.rb +++ b/core/spec/models/spree/variant_spec.rb @@ -7,6 +7,28 @@ let!(:variant) { create(:variant) } + describe '.non_template_variants' do + let(:option_type) { create(:option_type, option_values: [option_value]) } + let(:option_value) { build(:option_value) } + let(:product) { create(:product, option_types: [option_type]) } + let!(:variant) { create(:variant, product: product) } + + subject { described_class.non_template_variants } + + it { is_expected.to contain_exactly(variant) } + end + + describe '.template_variants' do + let(:option_type) { create(:option_type, option_values: [option_value]) } + let(:option_value) { build(:option_value) } + let(:product) { create(:product, option_types: [option_type]) } + let!(:variant) { create(:variant, product: product) } + + subject { described_class.template_variants } + + it { is_expected.to contain_exactly(product.master) } + end + describe 'delegates' do let(:product) { build(:product) } let(:variant) { build(:variant, product: product) } From 6b4d76dfdce276303523c02fcee261d179e38eee Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 9 Oct 2024 08:30:59 +0200 Subject: [PATCH 2/2] Variant search: Exclude template variants Currently, one can add template variants to one's cart, but that should not be possible. This commit makes sure that the variant search won't return template variants. --- core/lib/spree/core/search/variant.rb | 4 ++-- core/spec/lib/search/variant_spec.rb | 10 ++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/core/lib/spree/core/search/variant.rb b/core/lib/spree/core/search/variant.rb index dc964a15aca..ca9fa77259d 100644 --- a/core/lib/spree/core/search/variant.rb +++ b/core/lib/spree/core/search/variant.rb @@ -22,7 +22,7 @@ class Variant def initialize(query_string, scope: Spree::Variant.all) @query_string = query_string - @scope = scope + @scope = scope.non_template_variants end # Searches the variants table using the ransack 'search_terms' defined on the class. @@ -39,7 +39,7 @@ def results @scope.ransack(search_term_params(word)).result.pluck(:id) end - Spree::Variant.where(id: matches.inject(:&)) + @scope.where(id: matches.inject(:&)) end private diff --git a/core/spec/lib/search/variant_spec.rb b/core/spec/lib/search/variant_spec.rb index 8d210e1d81c..bb633420774 100644 --- a/core/spec/lib/search/variant_spec.rb +++ b/core/spec/lib/search/variant_spec.rb @@ -27,6 +27,16 @@ def refute_found(query_string, variant) it { refute_found("bca", variant) } end + context "with a template variant" do + let!(:option_type) { create(:option_type, option_values: [option_value]) } + let(:option_value) { build(:option_value) } + let(:product) { create(:product, option_types: [option_type], sku: "TEMPLATE") } + let(:variant) { create(:variant, product: product, sku: "NOT_TEMPLATE") } + + it { refute_found("TEMPLATE", product.master) } + it { assert_found("NOT_TEMPLATE", variant) } + end + context "by product" do it { assert_found("My Special Product", variant) } it { assert_found("My Spec", variant) }