使用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 %>
當我嘗試使用此答案中的代碼時,它仍然不會過濾金屬產品。 – Jessa
在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。 –
我得到19的單個結果,它對應於OptionValue的金屬ID。 – Jessa