22
33module Spree
44 module Core
5- # THIS FILE SHOULD BE OVER-RIDDEN IN YOUR SITE EXTENSION!
6- # the exact code probably won't be useful, though you're welcome to modify and reuse
7- # the current contents are mainly for testing and documentation
8-
9- # To override this file...
10- # 1) Make a copy of it in your sites local /lib/spree folder
11- # 2) Add it to the config load path, or require it in an initializer, e.g...
5+ # THIS FILE SHOULD BE OVER-RIDDEN IN YOUR APP!
126 #
13- # # config/initializers/spree.rb
14- # require 'spree/product_filters'
7+ # Use this code as a reference or a starting point for your own product
8+ # filters.
159 #
10+ # To override this file make a copy of it in lib/spree/core and modify it.
1611
17- # set up some basic filters for use with products
12+ # Each filter has two parts:
13+ #
14+ # * a parametrized named scope which expects a list of labels
15+ # * an object which describes/defines the filter
1816 #
19- # Each filter has two parts
20- # * a parametrized named scope which expects a list of labels
21- # * an object which describes/defines the filter
17+ # The filter description has the following components:
2218 #
23- # The filter description has three components
24- # * a name, for displaying on pages
25- # * a named scope which will 'execute' the filter
26- # * a mapping of presentation labels to the relevant condition (in the context of the named scope)
27- # * an optional list of labels and values (for use with object selection - see taxons examples below)
19+ # * a name for displaying on pages
20+ # * a named scope which filters the products
21+ # * a mapping of presentation labels to the relevant condition (in the
22+ # context of the named scope)
23+ # * an optional list of labels and values (for use with object selection -
24+ # see taxons examples below)
2825 #
29- # The named scopes here have a suffix '_any', following Ransack's convention for a
30- # scope which returns results which match any of the inputs. This is purely a convention,
31- # but might be a useful reminder.
26+ # The named scopes here have a suffix '_any', following Ransack's convention
27+ # for a scope which returns results which match any of the inputs. This is
28+ # purely a convention, but might be a useful reminder.
3229 #
33- # When creating a form, the name of the checkbox group for a filter F should be
34- # the name of F's scope with [] appended, eg "price_range_any[]", and for
35- # each label you should have a checkbox with the label as its value. On submission,
36- # Rails will send the action a hash containing (among other things) an array named
30+ # When creating a form, the name of the checkbox group for a filter F should
31+ # be the name of F's scope with [] appended, e.g. "price_range_any[]", and
32+ # for each label you should have a checkbox with the label as its value. On
33+ # submission, Rails will send the action a hash containing an array named
3734 # after the scope whose values are the active labels.
3835 #
39- # Ransack will then convert this array to a call to the named scope with the array
40- # contents, and the named scope will build a query with the disjunction of the conditions
41- # relating to the labels, all relative to the scope's context.
36+ # Ransack will then convert this array to a call to the named scope with the
37+ # array contents, and the named scope will build a query with the
38+ # disjunction of the conditions relating to the labels, all relative to the
39+ # scope's context.
4240 #
43- # The details of how/when filters are used is a detail for specific models (eg products
44- # or taxons), eg see the taxon model/controller.
45-
46- # See specific filters below for concrete examples.
41+ # The details of how/when filters are used is a detail for specific models.
42+ # For example, see the Taxon model/controller. See specific filters below
43+ # for concrete examples.
4744 module ProductFilters
48- # Example: filtering by price
49- # The named scope just maps incoming labels onto their conditions, and builds the conjunction
50- # 'price' is in the base scope's context (ie, "select foo from products where ...") so
51- # we can access the field right away
52- # The filter identifies which scope to use, then sets the conditions for each price range
45+ # Example: Filtering by price
5346 #
54- # If user checks off three different price ranges then the argument passed to
55- # below scope would be something like ["$10 - $15", "$15 - $18", "$18 - $20"]
47+ # The named scope just maps incoming labels onto their conditions, and
48+ # builds the conjunction 'price' is in the base scope's context (e.g.
49+ # "select foo from products where ...") so we can access the field right
50+ # away. The filter identifies which scope to use, then sets the
51+ # conditions for each price range.
5652 #
53+ # If user checks off three different price ranges then the argument passed
54+ # to below scope would be something like ["$10 - $15", "$15 - $18", "$18 -
55+ # $20"].
5756 Spree ::Product . add_search_scope :price_range_any do |*opts |
58- conds = opts . map { |element | Spree ::Core ::ProductFilters . price_filter [ :conds ] [ element ] } . reject ( &:nil? )
59- scope = conds . shift
60- conds . each do |new_scope |
61- scope = scope . or ( new_scope )
62- end
57+ scope = opts . filter_map { |element |
58+ Spree ::Core ::ProductFilters . price_filter [ :conds ] [ element ]
59+ } . inject { |scope1 , scope2 | scope1 . or ( scope2 ) }
6360
6461 Spree ::Product . joins ( master : :prices ) . where ( scope )
6562 end
@@ -83,24 +80,26 @@ def self.price_filter
8380 }
8481 end
8582
86- # Example: filtering by possible brands
83+ # Example: Filtering by possible brands
8784 #
88- # First, we define the scope. Two interesting points here: (a) we run our conditions
89- # in the scope where the info for the 'brand' property has been loaded; and (b)
90- # because we may want to filter by other properties too, we give this part of the
91- # query a unique name (which must be used in the associated conditions too).
85+ # First, we define the scope. Two interesting points here:
9286 #
93- # Secondly, the filter. Instead of a static list of values, we pull out all existing
94- # brands from the db, and then build conditions which test for string equality on
95- # the (uniquely named) field "p_brand.value". There's also a test for brand info
96- # being blank: note that this relies on with_property doing a left outer join
97- # rather than an inner join.
87+ # * we run our conditions in the scope where the info for the 'brand'
88+ # property has been loaded; and
89+ # * because we may want to filter by other properties too, we give this
90+ # part of the query a unique name (which must be used in the
91+ # associated conditions too).
92+ #
93+ # Second, instead of a static list of values, we pull out all existing
94+ # brands from the database. Then we build conditions which test for string
95+ # equality on the (uniquely named) field "p_brand.value". There's also a
96+ # test for brand info being blank. Note that this relies on
97+ # `with_property` doing a left outer join rather than an inner join.
9898 Spree ::Product . add_search_scope :brand_any do |*opts |
99- conds = opts . map { |value | ProductFilters . brand_filter [ :conds ] [ value ] } . reject ( &:nil? )
100- scope = conds . shift
101- conds . each do |new_scope |
102- scope = scope . or ( new_scope )
103- end
99+ scope = opts . filter_map { |value |
100+ Spree ::Core ::ProductFilters . brand_filter [ :conds ] [ value ]
101+ } . inject { |scope1 , scope2 | scope1 . or ( scope2 ) }
102+
104103 Spree ::Product . with_property ( 'brand' ) . where ( scope )
105104 end
106105
@@ -117,25 +116,27 @@ def self.brand_filter
117116 }
118117 end
119118
120- # Example: a parameterized filter
121- # The filter above may show brands which aren't applicable to the current taxon,
122- # so this one only shows the brands that are relevant to a particular taxon and
123- # its descendants.
119+ # Example: A parameterized filter
120+ #
121+ # The filter above may show brands which aren't applicable to the current
122+ # taxon, so this one only shows the brands that are relevant to a
123+ # particular taxon and its descendants.
124124 #
125- # We don't have to give a new scope since the conditions here are a subset of the
126- # more general filter, so decoding will still work - as long as the filters on a
127- # page all have unique names (ie, you can't use the two brand filters together
128- # if they use the same scope). To be safe, the code uses a copy of the scope.
125+ # We don't need to give a new scope since the conditions here are a subset
126+ # of the more general filter, so decoding will still work as long as the
127+ # filters on a page all have unique names (i.e. you can't use the two
128+ # brand filters together if they use the same scope). To be safe, the code
129+ # uses a copy of the scope.
129130 #
130- # HOWEVER: what happens if we want a more precise scope? we can't pass
131- # parametrized scope names to Ransack, only atomic names, so couldn 't ask
132- # for taxon T's customized filter to be used. BUT: we can arrange for the form
133- # to pass back a hash instead of an array, where the key acts as the ( taxon)
134- # parameter and value is its label array, and then get a modified named scope
135- # to get its conditions from a particular filter.
131+ # However, if we want a more precise scope, we can't pass parametrized
132+ # scope names to Ransack, only atomic names. We can 't ask for taxon T's
133+ # customized filter to be used, but we can arrange for the form to pass
134+ # back a hash instead of an array where the key acts as the taxon
135+ # parameter and value is its label array, and then get a modified named
136+ # scope to get its conditions from a particular filter.
136137 #
137- # The brand-finding code can be simplified if a few more named scopes were added to
138- # the product properties model.
138+ # The brand-finding code could be simplified if a few more named scopes
139+ # were added to the product properties model.
139140 Spree ::Product . add_search_scope :selective_brand_any do |*opts |
140141 Spree ::Product . brand_any ( *opts )
141142 end
0 commit comments