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 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 &lt; 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
... ...