2016-02-04 27 views
1

使用Spree Commerce 3.0穩定版,我需要編寫自定義產品過濾器以僅顯示至少有一個變體與所選OptionValue匹配的產品。通過Spree中的選項過濾產品

我有一個過濾器,在複選框中顯示正確的選項列表,但選擇一個選項不會更改返回的產品。

在這個例子中,產品在多個「金屬」選項(鉑金,白金,黃金,白銀等)可用。我已經設置了價格範圍過濾器,並且它工作正常。

如何讓產品按選項過濾?

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 %> 

回答

3

大衛格羅斯的上面的答案爲我工作,雖然我使用顏色選項。以下是我的代碼的外觀和執行步驟。

1)複製product_filters.rb的未經編輯的版本LIB/product_filters.rb

2)初始化它:在初始化/ 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)新的過濾器添加到應用程序/模型/大禮包/ 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

我不知道到底是什麼狂歡:: Product.scope正在做,但嘗試將其更改爲狂歡:: 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 
+0

當我嘗試使用此答案中的代碼時,它仍然不會過濾金屬產品。 – Jessa

+0

在rails控制檯中,它在運行時會返回什麼。 option_values =施普雷:: OptionValue.where(:呈現=> [ '黃金']弄平。).joins(:option_type)。凡(施普雷:: OptionType.table_name => {:名稱=> '金屬'} ).pluck(「#{Spree :: OptionValue.table_name} .id」)。 我相信ProductFilter.option_with_values中的option_values局部變量是空的,如果是這種情況,它將返回option_scope。 –

+0

我得到19的單個結果,它對應於OptionValue的金屬ID。 – Jessa