通过Spree中的选项过滤产品

时间:2016-02-04 01:28:54

标签: ruby-on-rails ruby activerecord spree ransack

随着Spree Commerce 3.0稳定,我需要编写一个自定义产品过滤器,以仅显示至少有一个变体与所选OptionValue匹配的产品。

我有一个过滤器,在复选框中显示正确的选项列表,但选择一个选项并不会更改返回的产品。

对于这个例子,产品有多个"金属"选项(铂金,白金,黄金,银等)。我已经设置了价格范围过滤器,并且它正常工作。

如何通过选项过滤产品?

我的lib/spree/product_filters.rb

module Spree
  module Core
    module ProductFilters
      # Example: filtering by price
      #   The named scope just maps incoming labels onto their conditions, and builds the conjunction
      #   'price' is in the base scope's context (ie, "select foo from products where ...") so
      #     we can access the field right away
      #   The filter identifies which scope to use, then sets the conditions for each price range
      #
      # If user checks off three different price ranges then the argument passed to
      # below scope would be something like ["$10 - $15", "$15 - $18", "$18 - $20"]
      #
      Spree::Product.add_search_scope :price_range_any do |*opts|
        conds = opts.map {|o| Spree::Core::ProductFilters.price_filter[:conds][o]}.reject { |c| c.nil? }
        scope = conds.shift
        conds.each do |new_scope|
          scope = scope.or(new_scope)
        end
        Spree::Product.joins(master: :default_price).where(scope)
      end

      def ProductFilters.format_price(amount)
        Spree::Money.new(amount)
      end

      def ProductFilters.price_filter
        v = Spree::Price.arel_table
        conds = [ [ Spree.t(:under_price, price: format_price(1000))     , v[:amount].lteq(1000)],
                  [ "#{format_price(1000)} - #{format_price(1500)}"        , v[:amount].in(1000..1500)],
                  [ "#{format_price(1500)} - #{format_price(1800)}"        , v[:amount].in(1500..1800)],
                  [ "#{format_price(1800)} - #{format_price(2000)}"        , v[:amount].in(1800..2000)],
                  [ Spree.t(:or_over_price, price: format_price(2000)) , v[:amount].gteq(2000)]]
        {
          name:   Spree.t(:price_range),
          scope:  :price_range_any,
          conds:  Hash[*conds.flatten],
          labels: conds.map { |k,v| [k, k] }
        }
      end



      # Test for discrete option values selection
      def ProductFilters.option_with_values(option_scope, option, values)
        # get values IDs for Option with name {@option} and value-names in {@values} for use in SQL below
        option_values = Spree::OptionValue.where(:presentation => [values].flatten).joins(:option_type).where(OptionType.table_name => {:name => option}).pluck("#{OptionValue.table_name}.id")
        return option_scope if option_values.empty?

        option_scope = option_scope.where("#{Product.table_name}.id in (select product_id from #{Variant.table_name} v left join spree_option_values_variants ov on ov.variant_id = v.id where ov.option_value_id in (?))", option_values)
        option_scope
        puts option_scope.inspect
      end

      # multi-option scope
      Spree::Product.scope :option_any,
                         lambda { |*opts|
                           option_scope = Spree::Product.includes(:variants_including_master)
                           opts.map { |opt|
                             # opt is an array => ['option-name', [value1, value2, value3, ...]]
                             option_scope = option_with_values(option_scope, *opt)
                           }
                           option_scope
                         }

      # metal filter
      def ProductFilters.metal_filter
        metals = Spree::OptionValue.where( :option_type_id => Spree::OptionType.find_by!(name: "Metal") ).order("position").map(&:presentation).compact.uniq
        {
            :name => "Metal Type",
            :scope => :option_any,
            :conds => nil,
            :option => 'metal',
            :labels => metals.map { |k| [k, k] }
        }
      end

    end
  end
end

我的app/views/spree/home/index.html.erb

<% content_for :sidebar do %>
  <div data-hook="homepage_sidebar_navigation">
    <%= render :partial => 'spree/shared/filters' %>
    <%= render :partial => 'spree/shared/taxonomies' %>
  </div>
<% end %>
<h2>Test!</h2>
<div data-hook="homepage_products">
  <% cache(cache_key_for_products) do %>
    <%= render :partial => 'spree/shared/products', :locals => { :products => @products } %>
  <% end %>
</div>

我的app/views/spree/shared/_filters.html.erb

<% filters = [Spree::Core::ProductFilters.metal_filter,Spree::Core::ProductFilters.price_filter] %>

<% unless filters.empty? %>
  <%= form_tag '', :method => :get, :id => 'sidebar_products_search' do %>
    <%= hidden_field_tag 'per_page', params[:per_page] %>
    <% filters.each do |filter| %> <i><%= filter[:name] %> </i>
      <% labels = filter[:labels] || filter[:conds].map {|m,c| [m,m]} %>
      <% next if labels.empty? %>
      <div class="navigation" data-hook="navigation">
        <h4 class="filter-title"> <%= filter[:name] %> </h4>
        <ul class="list-group">
          <% labels.each do |nm,val| %>
            <% label = "#{filter[:name]}_#{nm}".gsub(/\s+/,'_') %>
            <li class="list-group-item">
              <input type="checkbox"
                     id="<%= label %>"
                     name="search[<%= filter[:scope].to_s %>][]"
                     value="<%= val %>"
                     <%= params[:search] && params[:search][filter[:scope]] && params[:search][filter[:scope]].include?(val.to_s) ? "checked" : "" %> />
              <label class="nowrap" for="<%= label %>"> <%= nm %> </label>
            </li>
          <% end %>
        </ul>
      </div>
    <% end %>
    <%= submit_tag Spree.t(:search), :name => nil, :class => 'btn btn-primary' %>
  <% end %>
<% end %>

2 个答案:

答案 0 :(得分:3)

上面的David Gross的答案对我有用,虽然我使用的是颜色选项。以下是我的代码的样子以及我为使其工作所采取的步骤。

1)将未经编辑的 product_filters.rb 版本复制到lib / product_filters.rb

2)初始化:在 initializers / spree.rb 中,添加:

require 'product_filters'

# Spree.config do |config| etc........

3)将此代码添加到 product_filters.rb

  def ProductFilters.option_with_values(option_scope, option, values)
    # get values IDs for Option with name {@option} and value-names in {@values} for use in SQL below
    option_values = Spree::OptionValue.where(:presentation => [values].flatten).joins(:option_type).where(OptionType.table_name => {:name => option}).pluck("#{OptionValue.table_name}.id")
    return option_scope if option_values.empty?

    option_scope = option_scope.where("#{Product.table_name}.id in (select product_id from #{Variant.table_name} v left join spree_option_values_variants ov on ov.variant_id = v.id where ov.option_value_id in (?))", option_values)
    option_scope
  end

  # option scope
  Spree::Product.add_search_scope :option_any do |*opts|
    option_scope = Spree::Product.includes(:variants_including_master)
    option_type = ProductFilters.colour_filter[:option]

    opts.map { |opt|
      # opt is an array => ['option-name', [value1, value2, value3, ...]]
      option_scope = ProductFilters.option_with_values(option_scope, option_type, *opt)
     }
     option_scope
  end

  # colour option - object that describes the filter.
  def ProductFilters.colour_filter
    # Get an array of possible colours (option type of 'colour')
    # e.g. returns ["Gold", "Black", "White", "Silver", "Purple", "Multicoloured"]
    colours = Spree::OptionValue.where(:option_type_id => Spree::OptionType.find_by_name("colour")).order("position").map(&:presentation).compact.uniq
    {
        :name => "Colour",
        :scope => :option_any,
        :conds => nil,
        :option => 'colour', # this is MANDATORY
        :class => "colour",
        :labels => colours.map { |k| [k, k] }
    }
  end

4)将新过滤器添加到 app / models / spree / taxons.rb ,使其显示在前端:

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.colour_filter if Spree::Core::ProductFilters.respond_to?(:colour_filter)
  fs
end

那应该是它。我希望有所帮助 - 让我知道我是否可以进一步提供帮助。不幸的是,Spree过滤文档是不存在的,所以我们必须做。

答案 1 :(得分:1)

我不知道Spree :: Product.scope正在做什么,但尝试将其更改为Spree :: Product.add_search_scope。您还缺少option_with_values中的参数OptionType,您可以使用ProductFilters.metal_filter [:option]。

Spree::Product.add_search_scope :option_any do |*opts|
  option_scope = Spree::Product.includes(:variants_including_master)
  option_type = ProductFilters.metal_filter[:option]

  opts.map { |opt|
    # opt is an array => ['option-name', [value1, value2, value3, ...]]
    option_scope = ProductFilters.option_with_values(option_scope, option_type, *opt)
   }
   option_scope
end