Skip to content

Instantly share code, notes, and snippets.

@yfkhar
Forked from vsokolov/Spree: Filter by brand.md
Created June 13, 2022 19:47
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save yfkhar/ef5d04e6bc9de468fff2608a63d0f6e0 to your computer and use it in GitHub Desktop.
Save yfkhar/ef5d04e6bc9de468fff2608a63d0f6e0 to your computer and use it in GitHub Desktop.
Spree: Filter Products by properties

Fix 'scoped' error for Rails 4

error: undefined method 'scoped' solution:

# config/initializers/scoped.rb
class ActiveRecord::Base
  # do things the modern way and silence Rails 4 deprecation warnings
 def self.scoped(options=nil)
   options ? where(nil).apply_finder_options(options, true) : where(nil)
 end
end

Add new filter

override product_filters.rb copy original file to lib/spree/product_filters.rb

# libs/spree/product_filters.rb
module Spree
  module Core
   module ProductFilters
      Spree::Product.add_search_scope :selective_brand_any do |*opts|
        Spree::Product.brand_any(*opts)
      end

      def ProductFilters.selective_brand_filter(taxon = nil)
        # your filter
      end
   end
  end
end

use filter on Taxon show page

# app/models/taxon_decorator.rb

Spree::Taxon.class_eval do

  def applicable_filters
    fs = []
    # fs << ProductFilters.taxons_below(self)
    ## unless it's a root taxon? left open for demo purposes

    fs << Spree::Core::ProductFilters.price_filter if Spree::Core::ProductFilters.respond_to?(:price_filter)
    #fs << Spree::Core::ProductFilters.brand_filter if Spree::Core::ProductFilters.respond_to?(:brand_filter)
    fs << Spree::Core::ProductFilters.selective_brand_filter(self) if Spree::Core::ProductFilters.respond_to?(:selective_brand_filter)
    fs
  end

end

Filter by Property - Show only values applicable to the current taxon

Filter by Brand

# lib/spree/product_filters.rb

module Spree
  module Core
   module ProductFilters
      @@taxon = nil
      
      Spree::Product.add_search_scope :brand_any do |*opts|
        filter = ProductFilters.brand_filter(@@taxon)
        conds = opts.map {|o| filter[:conds][o]}.reject { |c| c.nil? }
        scope = conds.shift
        conds.each do |new_scope|
          scope = scope.or(new_scope)
        end
        Spree::Product.with_property('brand').where(scope)
      end


      def ProductFilters.brand_filter(taxon = nil)
        taxon ||= Spree::Taxonomy.first.root
        @@taxon = taxon

        property = Spree::Property.find_by(name: 'brand')

        scope = Spree::ProductProperty.where(property: property).
           joins(product: :taxons).
           where("#{Spree::Taxon.table_name}.id" => [taxon] + taxon.descendants)

        rows = scope.pluck(:value).uniq

        pp = Spree::ProductProperty.arel_table

        conds = Hash[*rows.map { |b| [b, pp[:value].eq(b)] }.flatten]

        {
          name:   'Brand',
          scope:  :brand_any,
          conds:  conds,
          labels: rows.sort.map { |k| [k, k] }
        }
      end

   end
  end
end

show filter on Taxon show page:

# app/models/taxon_decorator.rb

Spree::Taxon.class_eval do

  def applicable_filters
    fs = []

    fs << Spree::Core::ProductFilters.price_filter if Spree::Core::ProductFilters.respond_to?(:price_filter)
    fs << Spree::Core::ProductFilters.selective_brand_filter(self) if Spree::Core::ProductFilters.respond_to?(:selective_brand_filter)
    fs
  end



end

example. Filter by property 'fit'

http://stackoverflow.com/questions/14853046/spree-search-filter-for-properties-and-variants

Filter by multiple properties

If you have several properties and select some values for each of them then it won't find any product.

To fix this create a custom search class and use it as a searcher class.

This solution was found at https://gist.github.com/killthekitten/4486585.

# lib/spree/custom_search.rb

module Spree
  class CustomSearch < Spree::Core::Search::Base

    protected
    def add_search_scopes(base_scope)
      statement = nil
      search.each do |property_name, property_values|
        name = property_name.gsub("_any", "").gsub("selective_","")
        property = Spree::Property.find_by_name(name)
        next unless property

        substatement = product_property[:property_id].eq(property.id).and(product_property[:value].eq(property_values.first))
        #substatement = Spree::Product.with_property_value(name, property_values.first)
        property_values[1..-1].each do |pv|
          substatement = substatement.or product_property[:value].eq(pv)
          #substatement = substatement.or Spree::Product.with_property_value(name, pv)
        end
        tail = product[:id].in(Spree::ProductProperty.select(:product_id).where(substatement).map(&:product_id))
        #ids = Spree::ProductProperty.select(:product_id).where(substatement).map(&:product_id)
        #tail = product[:id].in(ids)
        statement = statement.nil? ? tail : statement.and(tail)
      end if search
      statement ? base_scope.where(statement) : base_scope
    end

    def prepare(params)
      super
      @properties[:product] = Spree::Product.arel_table
      @properties[:product_property] = Spree::ProductProperty.arel_table
    end
  end
end

Use our custom searcher:

# config/initializers/spree.rb
...
require "#{Rails.root}/lib/spree/custom_search"
Spree::Config.searcher_class = Spree::CustomSearch
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment