Skip to content

Instantly share code, notes, and snippets.

@maxivak
Last active March 16, 2021 01:31
Show Gist options
  • Save maxivak/cc73b88699c9c6b45a95 to your computer and use it in GitHub Desktop.
Save maxivak/cc73b88699c9c6b45a95 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
@bricesanchez
Copy link

@kjvenky Comment or remove fs lines you want to disable in applicable_filters.

Example :

# 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

@moholtzberg
Copy link

I get an an error uninitialized constant Spree::CustomSearch (NameError) when trying to add this custom searcher class

@pgouv
Copy link

pgouv commented May 19, 2016

There is a bug with this code.

Above code produces wrong sql when having multiple values. name = "blah" and value="blah" or value="meh" or value="meh2" not
name = "blah" and (value="blah" or value="meh" or value="meh2")

conditions = product_property[:value].eq(property_values.first)
property_values[1..-1].each do |pv|
  conditions = conditions.or product_property[:value].eq(pv)
end
substatement = product_property[:property_id].eq(property.id).and(conditions)

@jakemumu
Copy link

jakemumu commented Oct 1, 2017

does this work with solidus also?

@juan267
Copy link

juan267 commented Jun 10, 2018

Awesome, thanks!

@MrFehr
Copy link

MrFehr commented Jun 11, 2019

Awesome, thanks!

Hey, did you get this to work in Solidus?

@MrFehr
Copy link

MrFehr commented Jun 11, 2019

does this work with solidus also?

Hey, did you have any luck getting this to work in Solidus?

@mureithi254
Copy link

Also waiting to be able to make this work on solidus,Any help will be greatlly appreciated.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment