Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
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

This comment has been minimized.

Copy link

commented Aug 19, 2015

Really useful, thanks for sharing !

@connecticus

This comment has been minimized.

Copy link

commented Sep 16, 2015

Awesome!

@kjvenky

This comment has been minimized.

Copy link

commented Sep 19, 2015

How do I remove a filter

@bricesanchez

This comment has been minimized.

Copy link

commented Nov 9, 2015

Does someone managed to make it work with translated products ?

@bricesanchez

This comment has been minimized.

Copy link

commented Nov 9, 2015

@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

This comment has been minimized.

Copy link

commented Dec 11, 2015

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

@parhs

This comment has been minimized.

Copy link

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

This comment has been minimized.

Copy link

commented Oct 1, 2017

does this work with solidus also?

@juan267

This comment has been minimized.

Copy link

commented Jun 10, 2018

Awesome, thanks!

@MrFehr

This comment has been minimized.

Copy link

commented Jun 11, 2019

Awesome, thanks!

Hey, did you get this to work in Solidus?

@MrFehr

This comment has been minimized.

Copy link

commented Jun 11, 2019

does this work with solidus also?

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

@mureithi254

This comment has been minimized.

Copy link

commented Jun 19, 2019

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
You can’t perform that action at this time.