Commit c9f2af4dc38a2ec68332b74159a2b6c0278668b8
1 parent
92d69dfb
Exists in
master
and in
21 other branches
No more Tire DSL
Showing
2 changed files
with
181 additions
and
145 deletions
Show diff stats
lib/searchkick/search.rb
... | ... | @@ -35,165 +35,196 @@ module Searchkick |
35 | 35 | |
36 | 36 | all = term == "*" |
37 | 37 | |
38 | - # TODO lose Tire DSL for more flexibility | |
39 | - s = | |
40 | - Tire::Search::Search.new do | |
41 | - query do | |
42 | - custom_filters_score do | |
43 | - query do | |
44 | - if !all | |
45 | - boolean do | |
46 | - must do | |
47 | - if options[:autocomplete] | |
48 | - match fields, term, analyzer: "searchkick_autocomplete_search" | |
49 | - else | |
50 | - dis_max do | |
51 | - query do | |
52 | - match fields, term, use_dis_max: false, boost: 10, operator: operator, analyzer: "searchkick_search" | |
53 | - end | |
54 | - query do | |
55 | - match fields, term, use_dis_max: false, boost: 10, operator: operator, analyzer: "searchkick_search2" | |
56 | - end | |
57 | - query do | |
58 | - match fields, term, use_dis_max: false, fuzziness: 1, max_expansions: 3, operator: operator, analyzer: "searchkick_search" | |
59 | - end | |
60 | - query do | |
61 | - match fields, term, use_dis_max: false, fuzziness: 1, max_expansions: 3, operator: operator, analyzer: "searchkick_search2" | |
62 | - end | |
63 | - end | |
64 | - end | |
65 | - end | |
66 | - if conversions_field and options[:conversions] != false | |
67 | - should do | |
68 | - nested path: conversions_field, score_mode: "total" do | |
69 | - query do | |
70 | - custom_score script: "doc['count'].value" do | |
71 | - match "query", term | |
72 | - end | |
73 | - end | |
74 | - end | |
75 | - end | |
76 | - end | |
77 | - end | |
78 | - end | |
79 | - end | |
80 | - if options[:boost] | |
81 | - filter do | |
82 | - filter :exists, field: options[:boost] | |
83 | - script "log(doc['#{options[:boost]}'].value + 2.718281828)" | |
84 | - end | |
38 | + if all | |
39 | + payload = { | |
40 | + match_all: {} | |
41 | + } | |
42 | + else | |
43 | + if options[:autocomplete] | |
44 | + payload = { | |
45 | + multi_match: { | |
46 | + fields: fields, | |
47 | + query: term, | |
48 | + analyzer: "searchkick_autocomplete_search" | |
49 | + } | |
50 | + } | |
51 | + else | |
52 | + shared_options = { | |
53 | + fields: fields, | |
54 | + query: term, | |
55 | + use_dis_max: false, | |
56 | + operator: operator | |
57 | + } | |
58 | + payload = { | |
59 | + dis_max: { | |
60 | + queries: [ | |
61 | + {multi_match: shared_options.merge(boost: 10, analyzer: "searchkick_search")}, | |
62 | + {multi_match: shared_options.merge(boost: 10, analyzer: "searchkick_search2")}, | |
63 | + {multi_match: shared_options.merge(fuzziness: 1, max_expansions: 3, analyzer: "searchkick_search")}, | |
64 | + {multi_match: shared_options.merge(fuzziness: 1, max_expansions: 3, analyzer: "searchkick_search2")}, | |
65 | + ] | |
66 | + } | |
67 | + } | |
68 | + end | |
69 | + | |
70 | + if conversions_field and options[:conversions] != false | |
71 | + # wrap payload in a bool query | |
72 | + payload = { | |
73 | + bool: { | |
74 | + must: payload, | |
75 | + should: { | |
76 | + nested: { | |
77 | + path: conversions_field, | |
78 | + score_mode: "total", | |
79 | + query: { | |
80 | + custom_score: { | |
81 | + query: { | |
82 | + match: { | |
83 | + query: term | |
84 | + } | |
85 | + }, | |
86 | + script: "doc['count'].value" | |
87 | + } | |
88 | + } | |
89 | + } | |
90 | + } | |
91 | + } | |
92 | + } | |
93 | + end | |
94 | + end | |
95 | + | |
96 | + custom_filters = [] | |
97 | + | |
98 | + if options[:boost] | |
99 | + custom_filters << { | |
100 | + filter: { | |
101 | + exists: { | |
102 | + field: options[:boost], | |
103 | + } | |
104 | + }, | |
105 | + script: "log(doc['#{options[:boost]}'].value + 2.718281828)" | |
106 | + } | |
107 | + end | |
108 | + | |
109 | + if options[:user_id] and personalize_field | |
110 | + custom_filters << { | |
111 | + filter: { | |
112 | + term: { | |
113 | + personalize_field => options[:user_id] | |
114 | + }, | |
115 | + }, | |
116 | + boost: 100 | |
117 | + } | |
118 | + end | |
119 | + | |
120 | + if custom_filters.any? | |
121 | + payload = { | |
122 | + custom_filters_score: { | |
123 | + query: payload, | |
124 | + filters: custom_filters, | |
125 | + score_mode: "total" | |
126 | + } | |
127 | + } | |
128 | + end | |
129 | + | |
130 | + payload = { | |
131 | + query: payload, | |
132 | + size: per_page | |
133 | + } | |
134 | + payload[:from] = offset if offset | |
135 | + payload[:explain] = options[:explain] if options[:explain] | |
136 | + | |
137 | + # order | |
138 | + if options[:order] | |
139 | + order = options[:order].is_a?(Enumerable) ? options[:order] : {options[:order] => :asc} | |
140 | + payload[:sort] = order | |
141 | + end | |
142 | + | |
143 | + # where | |
144 | + # TODO expand or | |
145 | + where_filters = | |
146 | + proc do |where| | |
147 | + filters = [] | |
148 | + (where || {}).each do |field, value| | |
149 | + if field == :or | |
150 | + value.each do |or_clause| | |
151 | + filters << {or: or_clause.map{|or_statement| {term: or_statement} }} | |
85 | 152 | end |
86 | - if options[:user_id] and personalize_field | |
87 | - filter do | |
88 | - filter :term, personalize_field => options[:user_id] | |
89 | - boost 100 | |
90 | - end | |
153 | + else | |
154 | + # expand ranges | |
155 | + if value.is_a?(Range) | |
156 | + value = {gte: value.first, (value.exclude_end? ? :lt : :lte) => value.last} | |
91 | 157 | end |
92 | - score_mode "total" | |
93 | - end | |
94 | - end | |
95 | - size per_page | |
96 | - from offset if offset | |
97 | - explain options[:explain] if options[:explain] | |
98 | - | |
99 | - # order | |
100 | - if options[:order] | |
101 | - order = options[:order].is_a?(Enumerable) ? options[:order] : {options[:order] => :asc} | |
102 | - sort do | |
103 | - order.each do |k, v| | |
104 | - by k, v | |
105 | - end | |
106 | - end | |
107 | - end | |
108 | - | |
109 | - # where | |
110 | - # TODO expand or | |
111 | - where_filters = | |
112 | - proc do |where| | |
113 | - filters = [] | |
114 | - (where || {}).each do |field, value| | |
115 | - if field == :or | |
116 | - value.each do |or_clause| | |
117 | - filters << {or: or_clause.map{|or_statement| {term: or_statement} }} | |
118 | - end | |
119 | - else | |
120 | - # expand ranges | |
121 | - if value.is_a?(Range) | |
122 | - value = {gte: value.first, (value.exclude_end? ? :lt : :lte) => value.last} | |
123 | - end | |
124 | 158 | |
125 | - if value.is_a?(Array) # in query | |
126 | - filters << {terms: {field => value}} | |
127 | - elsif value.is_a?(Hash) | |
128 | - value.each do |op, op_value| | |
129 | - if op == :not # not equal | |
130 | - if op_value.is_a?(Array) | |
131 | - filters << {not: {terms: {field => op_value}}} | |
132 | - else | |
133 | - filters << {not: {term: {field => op_value}}} | |
134 | - end | |
135 | - else | |
136 | - range_query = | |
137 | - case op | |
138 | - when :gt | |
139 | - {from: op_value, include_lower: false} | |
140 | - when :gte | |
141 | - {from: op_value, include_lower: true} | |
142 | - when :lt | |
143 | - {to: op_value, include_upper: false} | |
144 | - when :lte | |
145 | - {to: op_value, include_upper: true} | |
146 | - else | |
147 | - raise "Unknown where operator" | |
148 | - end | |
149 | - filters << {range: {field => range_query}} | |
150 | - end | |
159 | + if value.is_a?(Array) # in query | |
160 | + filters << {terms: {field => value}} | |
161 | + elsif value.is_a?(Hash) | |
162 | + value.each do |op, op_value| | |
163 | + if op == :not # not equal | |
164 | + if op_value.is_a?(Array) | |
165 | + filters << {not: {terms: {field => op_value}}} | |
166 | + else | |
167 | + filters << {not: {term: {field => op_value}}} | |
151 | 168 | end |
152 | 169 | else |
153 | - filters << {term: {field => value}} | |
170 | + range_query = | |
171 | + case op | |
172 | + when :gt | |
173 | + {from: op_value, include_lower: false} | |
174 | + when :gte | |
175 | + {from: op_value, include_lower: true} | |
176 | + when :lt | |
177 | + {to: op_value, include_upper: false} | |
178 | + when :lte | |
179 | + {to: op_value, include_upper: true} | |
180 | + else | |
181 | + raise "Unknown where operator" | |
182 | + end | |
183 | + filters << {range: {field => range_query}} | |
154 | 184 | end |
155 | 185 | end |
186 | + else | |
187 | + filters << {term: {field => value}} | |
156 | 188 | end |
157 | - filters | |
158 | 189 | end |
159 | - | |
160 | - where_filters.call(options[:where]).each do |f| | |
161 | - type, value = f.first | |
162 | - filter type, value | |
163 | 190 | end |
191 | + filters | |
192 | + end | |
164 | 193 | |
165 | - # facets | |
166 | - if options[:facets] | |
167 | - facets = options[:facets] || {} | |
168 | - if facets.is_a?(Array) # convert to more advanced syntax | |
169 | - facets = Hash[ facets.map{|f| [f, {}] } ] | |
170 | - end | |
194 | + # filters | |
195 | + filters = where_filters.call(options[:where]) | |
196 | + if filters.any? | |
197 | + payload[:filter] = { | |
198 | + and: filters | |
199 | + } | |
200 | + end | |
171 | 201 | |
172 | - facets.each do |field, facet_options| | |
173 | - facet_filters = where_filters.call(facet_options[:where]) | |
174 | - facet field do | |
175 | - terms field | |
176 | - if facet_filters.size == 1 | |
177 | - type, value = facet_filters.first.first | |
178 | - facet_filter type, value | |
179 | - elsif facet_filters.size > 1 | |
180 | - facet_filter :and, *facet_filters | |
181 | - end | |
182 | - end | |
183 | - end | |
184 | - end | |
202 | + # facets | |
203 | + if options[:facets] | |
204 | + facets = options[:facets] || {} | |
205 | + if facets.is_a?(Array) # convert to more advanced syntax | |
206 | + facets = Hash[ facets.map{|f| [f, {}] } ] | |
185 | 207 | end |
186 | 208 | |
187 | - payload = s.to_hash | |
209 | + payload[:facets] = {} | |
210 | + facets.each do |field, facet_options| | |
211 | + payload[:facets][field] = { | |
212 | + terms: { | |
213 | + field: field | |
214 | + } | |
215 | + } | |
188 | 216 | |
189 | - if all | |
190 | - payload[:query][:custom_filters_score][:query][:match_all] = {} | |
217 | + facet_filters = where_filters.call(facet_options[:where]) | |
218 | + if facet_filters | |
219 | + payload[:facets][field][:facet_filter] = { | |
220 | + and: { | |
221 | + filters: facet_filters | |
222 | + } | |
223 | + } | |
224 | + end | |
225 | + end | |
191 | 226 | end |
192 | 227 | |
193 | - # An empty array will cause only the _id and _type for each hit to be returned | |
194 | - # http://www.elasticsearch.org/guide/reference/api/search/fields/ | |
195 | - payload[:fields] = [] if load | |
196 | - | |
197 | 228 | # suggestions |
198 | 229 | if options[:suggest] |
199 | 230 | suggest_fields = (@searchkick_options[:suggest] || []).map(&:to_s) |
... | ... | @@ -211,6 +242,10 @@ module Searchkick |
211 | 242 | end |
212 | 243 | end |
213 | 244 | |
245 | + # An empty array will cause only the _id and _type for each hit to be returned | |
246 | + # http://www.elasticsearch.org/guide/reference/api/search/fields/ | |
247 | + payload[:fields] = [] if load | |
248 | + | |
214 | 249 | search = Tire::Search::Search.new(index_name, load: load, payload: payload) |
215 | 250 | Searchkick::Results.new(search.json, search.options.merge(term: term)) |
216 | 251 | end | ... | ... |
test/boost_test.rb
... | ... | @@ -25,10 +25,11 @@ class TestBoost < Minitest::Unit::TestCase |
25 | 25 | |
26 | 26 | def test_boost |
27 | 27 | store [ |
28 | - {name: "Organic Tomato A"}, | |
29 | - {name: "Tomato B", orders_count: 10} | |
28 | + {name: "Tomato A"}, | |
29 | + {name: "Tomato B", orders_count: 10}, | |
30 | + {name: "Tomato C", orders_count: 100} | |
30 | 31 | ] |
31 | - assert_order "tomato", ["Tomato B", "Organic Tomato A"], boost: "orders_count" | |
32 | + assert_order "tomato", ["Tomato C", "Tomato B", "Tomato A"], boost: "orders_count" | |
32 | 33 | end |
33 | 34 | |
34 | 35 | def test_boost_zero | ... | ... |