Commit c9f2af4dc38a2ec68332b74159a2b6c0278668b8

Authored by Andrew Kane
1 parent 92d69dfb

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,165 +35,196 @@ module Searchkick
35 35
36 all = term == "*" 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 end 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 end 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 end 168 end
152 else 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 end 184 end
155 end 185 end
  186 + else
  187 + filters << {term: {field => value}}
156 end 188 end
157 - filters  
158 end 189 end
159 -  
160 - where_filters.call(options[:where]).each do |f|  
161 - type, value = f.first  
162 - filter type, value  
163 end 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 end 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 end 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 # suggestions 228 # suggestions
198 if options[:suggest] 229 if options[:suggest]
199 suggest_fields = (@searchkick_options[:suggest] || []).map(&:to_s) 230 suggest_fields = (@searchkick_options[:suggest] || []).map(&:to_s)
@@ -211,6 +242,10 @@ module Searchkick @@ -211,6 +242,10 @@ module Searchkick
211 end 242 end
212 end 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 search = Tire::Search::Search.new(index_name, load: load, payload: payload) 249 search = Tire::Search::Search.new(index_name, load: load, payload: payload)
215 Searchkick::Results.new(search.json, search.options.merge(term: term)) 250 Searchkick::Results.new(search.json, search.options.merge(term: term))
216 end 251 end
test/boost_test.rb
@@ -25,10 +25,11 @@ class TestBoost &lt; Minitest::Unit::TestCase @@ -25,10 +25,11 @@ class TestBoost &lt; Minitest::Unit::TestCase
25 25
26 def test_boost 26 def test_boost
27 store [ 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 end 33 end
33 34
34 def test_boost_zero 35 def test_boost_zero