From 272f62d8cd0a0dee38c6950fb6a4af59e19fa0fa Mon Sep 17 00:00:00 2001 From: Rainer Dema Date: Thu, 28 Sep 2023 16:27:03 +0200 Subject: [PATCH] Add ransacker for product variants option values Introduce a ransacker `variants_option_values` on Spree::Product. This ransacker enables dynamic filtering combined with ransack on product variants option values. For now, this attribute is geared towards usage in the admin interface. https://activerecord-hackery.github.io/ransack/going-further/ransackers/ --- core/app/models/spree/product.rb | 20 ++++++++++- core/spec/models/spree/product_spec.rb | 46 ++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/core/app/models/spree/product.rb b/core/app/models/spree/product.rb index dffd1b69715..af9f7330b08 100644 --- a/core/app/models/spree/product.rb +++ b/core/app/models/spree/product.rb @@ -131,8 +131,26 @@ def find_or_build_master alias :options :product_option_types + # The :variants_option_values ransacker filters Spree::Products based on + # variant option values ids. + # + # Usage: + # To fetch products with variants having specific option values, use: + # + # Spree::Product.ransack( + # variants_option_values_in: [option_value_id1, option_value_id2] + # ).result + ransacker :variants_option_values, formatter: proc { |v| + joins(variants_including_master: :option_values) + .where(spree_option_values: { id: v }) + .distinct + .pluck(:id) + } do |parent| + parent.table[:id] + end + self.allowed_ransackable_associations = %w[stores variants_including_master master variants] - self.allowed_ransackable_attributes = %w[name slug] + self.allowed_ransackable_attributes = %w[name slug variants_option_values] self.allowed_ransackable_scopes = %i[available with_discarded with_all_variant_sku_cont with_kept_variant_sku_cont] # @return [Boolean] true if there are any variants diff --git a/core/spec/models/spree/product_spec.rb b/core/spec/models/spree/product_spec.rb index 3cd890072cd..82a90cdcc62 100644 --- a/core/spec/models/spree/product_spec.rb +++ b/core/spec/models/spree/product_spec.rb @@ -641,6 +641,52 @@ class Extension < Spree::Base end end + context "ransacker :variants_option_values" do + it "filters products based on option values of their variants" do + product_1 = create(:product) + option_value_1 = create(:option_value) + create(:variant, product: product_1, option_values: [option_value_1]) + + result = Spree::Product.ransack(variants_option_values_in: [option_value_1.id]).result + expect(result).to contain_exactly(product_1) + end + + it "returns multiple products for the same option value" do + product_1 = create(:product) + product_2 = create(:product) + option_value_1 = create(:option_value) + create(:variant, product: product_1, option_values: [option_value_1]) + create(:variant, product: product_2, option_values: [option_value_1]) + + result = Spree::Product.ransack(variants_option_values_in: [option_value_1.id]).result + expect(result).to contain_exactly(product_1, product_2) + end + + it "returns products that match any of the provided option value IDs" do + product_1 = create(:product) + product_2 = create(:product) + option_value_1 = create(:option_value) + option_value_2 = create(:option_value) + create(:variant, product: product_1, option_values: [option_value_1]) + create(:variant, product: product_2, option_values: [option_value_2]) + + result = Spree::Product.ransack(variants_option_values_in: [option_value_1.id, option_value_2.id]).result + expect(result).to contain_exactly(product_1, product_2) + end + + it "doesn't return products that have other option values not in the query" do + product_1 = create(:product) + product_2 = create(:product) + option_value_1 = create(:option_value) + option_value_2 = create(:option_value) + create(:variant, product: product_1, option_values: [option_value_1]) + create(:variant, product: product_2, option_values: [option_value_2]) + + result = Spree::Product.ransack(variants_option_values_in: [option_value_1.id]).result + expect(result).not_to include(product_2) + end + end + describe "ransack scopes" do context "available scope" do subject { described_class.ransack(available: true).result }