search_flip 2.3.2 → 3.0.0.beta

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,4 +1,3 @@
1
-
2
1
  module SearchFlip
3
2
  # The SearchFlip::Criteria class serves the purpose of chaining various
4
3
  # filtering and aggregation methods. Each chainable method creates a new
@@ -14,15 +13,15 @@ module SearchFlip
14
13
  # CommentIndex.sort("_doc").find_each { |comment| "..." }
15
14
 
16
15
  class Criteria
17
- include SearchFlip::Filterable
18
- include SearchFlip::PostFilterable
19
- include SearchFlip::Aggregatable
16
+ include Filterable
17
+ include PostFilterable
18
+ include Aggregatable
20
19
  extend Forwardable
21
20
 
22
21
  attr_accessor :target, :profile_value, :source_value, :sort_values, :highlight_values, :suggest_values,
23
22
  :offset_value, :limit_value, :includes_values, :eager_load_values, :preload_values, :failsafe_value,
24
- :scroll_args, :custom_value, :terminate_after_value, :timeout_value, :preference_value,
25
- :search_type_value, :routing_value, :track_total_hits_value, :explain_value
23
+ :scroll_args, :custom_value, :terminate_after_value, :timeout_value, :preference_value, :search_type_value,
24
+ :routing_value, :track_total_hits_value, :explain_value
26
25
 
27
26
  # Creates a new criteria while merging the attributes (constraints,
28
27
  # settings, etc) of the current criteria with the attributes of another one
@@ -58,15 +57,11 @@ module SearchFlip
58
57
  criteria.includes_values = (criteria.includes_values || []) + other.includes_values if other.includes_values
59
58
  criteria.preload_values = (criteria.preload_values || []) + other.preload_values if other.preload_values
60
59
  criteria.eager_load_values = (criteria.eager_load_values || []) + other.eager_load_values if other.eager_load_values
61
- criteria.search_values = (criteria.search_values || []) + other.search_values if other.search_values
62
60
  criteria.must_values = (criteria.must_values || []) + other.must_values if other.must_values
63
61
  criteria.must_not_values = (criteria.must_not_values || []) + other.must_not_values if other.must_not_values
64
- criteria.should_values = (criteria.should_values || []) + other.should_values if other.should_values
65
62
  criteria.filter_values = (criteria.filter_values || []) + other.filter_values if other.filter_values
66
- criteria.post_search_values = (criteria.post_search_values || []) + other.post_search_values if other.post_search_values
67
63
  criteria.post_must_values = (criteria.post_must_values || []) + other.post_must_values if other.post_must_values
68
64
  criteria.post_must_not_values = (criteria.post_must_not_values || []) + other.post_must_not_values if other.post_must_not_values
69
- criteria.post_should_values = (criteria.post_should_values || []) + other.post_should_values if other.post_should_values
70
65
  criteria.post_filter_values = (criteria.post_filter_values || []) + other.post_filter_values if other.post_filter_values
71
66
 
72
67
  criteria.highlight_values = (criteria.highlight_values || {}).merge(other.highlight_values) if other.highlight_values
@@ -190,37 +185,6 @@ module SearchFlip
190
185
  end
191
186
  end
192
187
 
193
- # Creates a new criteria while removing all specified scopes. Currently,
194
- # you can unscope :search, :post_search, :sort, :highlight, :suggest, :custom
195
- # and :aggregate.
196
- #
197
- # @example
198
- # CommentIndex.search("hello world").aggregate(:username).unscope(:search, :aggregate)
199
- #
200
- # @param scopes [Symbol] All scopes that you want to remove
201
- #
202
- # @return [SearchFlip::Criteria] A newly created extended criteria
203
-
204
- def unscope(*scopes)
205
- warn "[DEPRECATION] unscope is deprecated and will be removed in search_flip 3"
206
-
207
- unknown = scopes - [:search, :post_search, :sort, :highlight, :suggest, :custom, :aggregate]
208
-
209
- raise(ArgumentError, "Can't unscope #{unknown.join(", ")}") if unknown.size > 0
210
-
211
- scopes = scopes.to_set
212
-
213
- fresh.tap do |criteria|
214
- criteria.search_values = nil if scopes.include?(:search)
215
- criteria.post_search_values = nil if scopes.include?(:post_search)
216
- criteria.sort_values = nil if scopes.include?(:sort)
217
- criteria.hightlight_values = nil if scopes.include?(:highlight)
218
- criteria.suggest_values = nil if scopes.include?(:suggest)
219
- criteria.custom_values = nil if scopes.include?(:custom)
220
- criteria.aggregation_values = nil if scopes.include?(:aggregate)
221
- end
222
- end
223
-
224
188
  # @api private
225
189
  #
226
190
  # Convenience method to have a unified conversion api.
@@ -231,6 +195,8 @@ module SearchFlip
231
195
  self
232
196
  end
233
197
 
198
+ alias_method :all, :criteria
199
+
234
200
  # Creates a new SearchFlip::Criteria.
235
201
  #
236
202
  # @param attributes [Hash] Attributes to initialize the Criteria with
@@ -251,7 +217,7 @@ module SearchFlip
251
217
  #
252
218
  # @return [SearchFlip::Criteria] Simply returns self
253
219
 
254
- ruby2_keywords def with_settings(*args)
220
+ def with_settings(*args)
255
221
  fresh.tap do |criteria|
256
222
  criteria.target = target.with_settings(*args)
257
223
  end
@@ -266,36 +232,17 @@ module SearchFlip
266
232
  def request
267
233
  res = {}
268
234
 
269
- if must_values || search_values || must_not_values || should_values || filter_values
270
- if connection.version.to_i >= 2
271
- res[:query] = {
272
- bool: {}
273
- .merge(must_values || search_values ? { must: (must_values || []) + (search_values || []) } : {})
274
- .merge(must_not_values ? { must_not: must_not_values } : {})
275
- .merge(should_values ? { should: should_values } : {})
276
- .merge(filter_values ? { filter: filter_values } : {})
277
- }
278
- else
279
- filters = (filter_values || []) + (must_not_values || []).map { |must_not_value| { not: must_not_value } }
280
-
281
- queries = {}
282
- .merge(must_values || search_values ? { must: (must_values || []) + (search_values || []) } : {})
283
- .merge(should_values ? { should: should_values } : {})
284
-
285
- res[:query] =
286
- if filters.size > 0
287
- {
288
- filtered: {}
289
- .merge(queries.size > 0 ? { query: { bool: queries } } : {})
290
- .merge(filter: filters.size > 1 ? { and: filters } : filters.first)
291
- }
292
- else
293
- { bool: queries }
294
- end
295
- end
235
+ if must_values || must_not_values || filter_values
236
+ res[:query] = {
237
+ bool: {
238
+ must: must_values.to_a,
239
+ must_not: must_not_values.to_a,
240
+ filter: filter_values.to_a
241
+ }.reject { |_, value| value.empty? }
242
+ }
296
243
  end
297
244
 
298
- res.update from: offset_value_with_default, size: limit_value_with_default
245
+ res.update(from: offset_value_with_default, size: limit_value_with_default)
299
246
 
300
247
  res[:track_total_hits] = track_total_hits_value unless track_total_hits_value.nil?
301
248
  res[:explain] = explain_value unless explain_value.nil?
@@ -306,26 +253,14 @@ module SearchFlip
306
253
  res[:sort] = sort_values if sort_values
307
254
  res[:aggregations] = aggregation_values if aggregation_values
308
255
 
309
- if post_must_values || post_search_values || post_must_not_values || post_should_values || post_filter_values
310
- if connection.version.to_i >= 2
311
- res[:post_filter] = {
312
- bool: {}
313
- .merge(post_must_values || post_search_values ? { must: (post_must_values || []) + (post_search_values || []) } : {})
314
- .merge(post_must_not_values ? { must_not: post_must_not_values } : {})
315
- .merge(post_should_values ? { should: post_should_values } : {})
316
- .merge(post_filter_values ? { filter: post_filter_values } : {})
317
- }
318
- else
319
- post_filters = (post_filter_values || []) + (post_must_not_values || []).map { |post_must_not_value| { not: post_must_not_value } }
320
-
321
- post_queries = {}
322
- .merge(post_must_values || post_search_values ? { must: (post_must_values || []) + (post_search_values || []) } : {})
323
- .merge(post_should_values ? { should: post_should_values } : {})
324
-
325
- post_filters_and_queries = post_filters + (post_queries.size > 0 ? [bool: post_queries] : [])
326
-
327
- res[:post_filter] = post_filters_and_queries.size > 1 ? { and: post_filters_and_queries } : post_filters_and_queries.first
328
- end
256
+ if post_must_values || post_must_not_values || post_filter_values
257
+ res[:post_filter] = {
258
+ bool: {
259
+ must: post_must_values.to_a,
260
+ must_not: post_must_not_values.to_a,
261
+ filter: post_filter_values.to_a
262
+ }.reject { |_, value| value.empty? }
263
+ }
329
264
  end
330
265
 
331
266
  res[:_source] = source_value unless source_value.nil?
@@ -633,6 +568,8 @@ module SearchFlip
633
568
  end
634
569
  end
635
570
 
571
+ # @api private
572
+ #
636
573
  # Returns the offset value or, if not yet set, the default limit value (0).
637
574
  #
638
575
  # @return [Fixnum] The offset value
@@ -658,6 +595,8 @@ module SearchFlip
658
595
  end
659
596
  end
660
597
 
598
+ # @api private
599
+ #
661
600
  # Returns the limit value or, if not yet set, the default limit value (30).
662
601
  #
663
602
  # @return [Fixnum] The limit value
@@ -678,7 +617,7 @@ module SearchFlip
678
617
  #
679
618
  # @return [SearchFlip::Criteria] A newly created extended criteria
680
619
 
681
- def paginate(page:, per_page: limit_value_with_default)
620
+ def paginate(page: 1, per_page: 30)
682
621
  page = [page.to_i, 1].max
683
622
  per_page = per_page.to_i
684
623
 
@@ -686,11 +625,11 @@ module SearchFlip
686
625
  end
687
626
 
688
627
  def page(value)
689
- paginate(page: value)
628
+ paginate(page: value, per_page: limit_value_with_default)
690
629
  end
691
630
 
692
631
  def per(value)
693
- paginate(page: offset_value_with_default / limit_value_with_default + 1, per_page: value)
632
+ paginate(page: 1 + (offset_value_with_default / limit_value_with_default), per_page: value)
694
633
  end
695
634
 
696
635
  # Fetches the records specified by the criteria in batches using the
@@ -811,17 +750,11 @@ module SearchFlip
811
750
 
812
751
  http_response =
813
752
  if scroll_args && scroll_args[:id]
814
- if connection.version.to_i >= 2
815
- http_request.post(
816
- "#{connection.base_url}/_search/scroll",
817
- params: request_params,
818
- json: { scroll: scroll_args[:timeout], scroll_id: scroll_args[:id] }
819
- )
820
- else
821
- http_request
822
- .headers(content_type: "text/plain")
823
- .post("#{connection.base_url}/_search/scroll", params: request_params.merge(scroll: scroll_args[:timeout]), body: scroll_args[:id])
824
- end
753
+ http_request.post(
754
+ "#{connection.base_url}/_search/scroll",
755
+ params: request_params,
756
+ json: { scroll: scroll_args[:timeout], scroll_id: scroll_args[:id] }
757
+ )
825
758
  elsif scroll_args
826
759
  http_request.post(
827
760
  "#{target.type_url}/_search",
@@ -886,7 +819,7 @@ module SearchFlip
886
819
  target.respond_to?(name, *args)
887
820
  end
888
821
 
889
- ruby2_keywords def method_missing(name, *args, &block)
822
+ def method_missing(name, *args, &block)
890
823
  if target.respond_to?(name)
891
824
  merge(target.send(name, *args, &block))
892
825
  else
@@ -1,5 +1,3 @@
1
-
2
1
  module SearchFlip
3
2
  class MethodNotImplemented < StandardError; end
4
3
  end
5
-
@@ -1,4 +1,3 @@
1
-
2
1
  module SearchFlip
3
2
  # The SearchFlip::Filterable mixin provides chainable methods like
4
3
  # #where, #exists, #range, etc to add search filters to a criteria.
@@ -11,7 +10,7 @@ module SearchFlip
11
10
  module Filterable
12
11
  def self.included(base)
13
12
  base.class_eval do
14
- attr_accessor :search_values, :must_values, :must_not_values, :should_values, :filter_values
13
+ attr_accessor :must_values, :must_not_values, :filter_values
15
14
  end
16
15
  end
17
16
 
@@ -30,11 +29,9 @@ module SearchFlip
30
29
  # @return [SearchFlip::Criteria] A newly created extended criteria
31
30
 
32
31
  def search(q, options = {})
33
- fresh.tap do |criteria|
34
- if q.to_s.strip.length > 0
35
- criteria.search_values = (search_values || []) + [query_string: { query: q, default_operator: :AND }.merge(options)]
36
- end
37
- end
32
+ return self if q.to_s.strip.length.zero?
33
+
34
+ must(query_string: { query: q, default_operator: :AND }.merge(options))
38
35
  end
39
36
 
40
37
  # Adds filters to your criteria for the supplied hash composed of
@@ -57,13 +54,13 @@ module SearchFlip
57
54
  def where(hash)
58
55
  hash.inject(fresh) do |memo, (key, value)|
59
56
  if value.is_a?(Array)
60
- memo.filter terms: { key => value }
57
+ memo.filter(terms: { key => value })
61
58
  elsif value.is_a?(Range)
62
- memo.filter range: { key => { gte: value.min, lte: value.max } }
59
+ memo.filter(range: { key => { gte: value.min, lte: value.max } })
63
60
  elsif value.nil?
64
- memo.exists_not key
61
+ memo.must_not(exists: { field: key })
65
62
  else
66
- memo.filter term: { key => value }
63
+ memo.filter(term: { key => value })
67
64
  end
68
65
  end
69
66
  end
@@ -88,13 +85,13 @@ module SearchFlip
88
85
  def where_not(hash)
89
86
  hash.inject(fresh) do |memo, (key, value)|
90
87
  if value.is_a?(Array)
91
- memo.must_not terms: { key => value }
88
+ memo.must_not(terms: { key => value })
92
89
  elsif value.is_a?(Range)
93
- memo.must_not range: { key => { gte: value.min, lte: value.max } }
90
+ memo.must_not(range: { key => { gte: value.min, lte: value.max } })
94
91
  elsif value.nil?
95
- memo.exists key
92
+ memo.filter(exists: { field: key })
96
93
  else
97
- memo.must_not term: { key => value }
94
+ memo.must_not(term: { key => value })
98
95
  end
99
96
  end
100
97
  end
@@ -109,9 +106,9 @@ module SearchFlip
109
106
  #
110
107
  # @return [SearchFlip::Criteria] A newly created extended criteria
111
108
 
112
- def filter(*args)
109
+ def filter(clause)
113
110
  fresh.tap do |criteria|
114
- criteria.filter_values = (filter_values || []) + args
111
+ criteria.filter_values = (filter_values || []) + Helper.wrap_array(clause)
115
112
  end
116
113
  end
117
114
 
@@ -125,9 +122,9 @@ module SearchFlip
125
122
  #
126
123
  # @return [SearchFlip::Criteria] A newly created extended criteria
127
124
 
128
- def must(*args)
125
+ def must(clause, bool_options = {})
129
126
  fresh.tap do |criteria|
130
- criteria.must_values = (must_values || []) + args
127
+ criteria.must_values = (must_values || []) + Helper.wrap_array(clause)
131
128
  end
132
129
  end
133
130
 
@@ -141,28 +138,45 @@ module SearchFlip
141
138
  #
142
139
  # @return [SearchFlip::Criteria] A newly created extended criteria
143
140
 
144
- def must_not(*args)
141
+ def must_not(clause)
145
142
  fresh.tap do |criteria|
146
- criteria.must_not_values = (must_not_values || []) + args
143
+ criteria.must_not_values = (must_not_values || []) + Helper.wrap_array(clause)
147
144
  end
148
145
  end
149
146
 
150
- # Adds raw should queries to the criteria.
147
+ # Returns all added queries and filters, including post filters, as a raw
148
+ # query.
151
149
  #
152
150
  # @example
153
- # CommentIndex.should(term: { state: "new" })
154
- # CommentIndex.should(range: { created_at: { gt: Time.parse"2016-01-01") }})
151
+ # CommentIndex.where(state: "new").search("text").to_query
152
+ # # => {:bool=>{:filter=>[{:term=>{:state=>"new"}}], :must=>[{:query_string=>{:query=>"text", ...}}]}}
155
153
  #
156
- # @param args [Array, Hash] The raw should query arguments
154
+ # @return [Hash] The raw query
155
+
156
+ def to_query
157
+ {
158
+ bool: {
159
+ must: must_values.to_a,
160
+ must_not: must_not_values.to_a + post_must_not_values.to_a,
161
+ filter: post_must_values.to_a + filter_values.to_a + post_filter_values.to_a
162
+ }.reject { |_, value| value.empty? }
163
+ }
164
+ end
165
+
166
+ # Adds a raw should query to the criteria.
167
+ #
168
+ # @example
169
+ # CommentIndex.should([
170
+ # { term: { state: "new" } },
171
+ # { term: { state: "reviewed" } }
172
+ # ])
173
+ #
174
+ # @param args [Array] The raw should query arguments
157
175
  #
158
176
  # @return [SearchFlip::Criteria] A newly created extended criteria
159
177
 
160
- def should(*args)
161
- warn "[DEPRECATION] should will change in search_flip 3. Please use .must(bool: { should: ... }) until release."
162
-
163
- fresh.tap do |criteria|
164
- criteria.should_values = (should_values || []) + args
165
- end
178
+ def should(clause)
179
+ must(bool: { should: clause })
166
180
  end
167
181
 
168
182
  # Adds a range filter to the criteria without being forced to specify the
@@ -181,10 +195,10 @@ module SearchFlip
181
195
  # @return [SearchFlip::Criteria] A newly created extended criteria
182
196
 
183
197
  def range(field, options = {})
184
- filter range: { field => options }
198
+ filter(range: { field => options })
185
199
  end
186
200
 
187
- # Adds a match all filter/query to the criteria, which simply matches all
201
+ # Adds a match all filter to the criteria, which simply matches all
188
202
  # documents. This can be eg be used within filter aggregations or for
189
203
  # filter chaining. Check out the Elasticsearch docs for further details.
190
204
  #
@@ -209,7 +223,7 @@ module SearchFlip
209
223
  # @return [SearchFlip::Criteria] A newly created extended criteria
210
224
 
211
225
  def match_all(options = {})
212
- filter match_all: options
226
+ filter(match_all: options)
213
227
  end
214
228
 
215
229
  # Adds an exists filter to the criteria, which selects all documents for
@@ -223,10 +237,10 @@ module SearchFlip
223
237
  # @return [SearchFlip::Criteria] A newly created extended criteria
224
238
 
225
239
  def exists(field)
226
- filter exists: { field: field }
240
+ filter(exists: { field: field })
227
241
  end
228
242
 
229
- # Adds an exists not filter to the criteria, which selects all documents
243
+ # Adds an exists not query to the criteria, which selects all documents
230
244
  # for which the specified field's value is null.
231
245
  #
232
246
  # @example
@@ -237,8 +251,7 @@ module SearchFlip
237
251
  # @return [SearchFlip::Criteria] A newly created extended criteria
238
252
 
239
253
  def exists_not(field)
240
- must_not exists: { field: field }
254
+ must_not(exists: { field: field })
241
255
  end
242
256
  end
243
257
  end
244
-
@@ -0,0 +1,13 @@
1
+ module SearchFlip
2
+ module Helper
3
+ def self.wrap_array(object)
4
+ if object.nil?
5
+ []
6
+ elsif object.respond_to?(:to_ary)
7
+ object.to_ary || [object]
8
+ else
9
+ [object]
10
+ end
11
+ end
12
+ end
13
+ end
@@ -1,4 +1,3 @@
1
-
2
1
  module SearchFlip
3
2
  # @api private
4
3
  #
@@ -26,21 +25,17 @@ module SearchFlip
26
25
  client.request = request.send(method, *args)
27
26
  end
28
27
  end
29
-
30
- ruby2_keywords method
31
28
  end
32
29
 
33
30
  [:get, :post, :put, :delete, :head].each do |method|
34
31
  define_method method do |*args|
35
32
  execute(method, *args)
36
33
  end
37
-
38
- ruby2_keywords method
39
34
  end
40
35
 
41
36
  private
42
37
 
43
- ruby2_keywords def execute(method, *args)
38
+ def execute(method, *args)
44
39
  response = request.send(method, *args)
45
40
 
46
41
  raise SearchFlip::ResponseError.new(code: response.code, body: response.body.to_s) unless response.status.success?
@@ -51,4 +46,3 @@ module SearchFlip
51
46
  end
52
47
  end
53
48
  end
54
-
@@ -1,4 +1,3 @@
1
-
2
1
  module SearchFlip
3
2
  # The SearchFlip::Index mixin makes your class correspond to an
4
3
  # Elasticsearch index. Your class can then create or delete the index, modify
@@ -248,13 +247,13 @@ module SearchFlip
248
247
  SearchFlip::Criteria.new(target: self)
249
248
  end
250
249
 
251
- def_delegators :criteria, :profile, :where, :where_not, :filter, :range, :match_all, :exists,
250
+ def_delegators :criteria, :all, :profile, :where, :where_not, :filter, :range, :match_all, :exists,
252
251
  :exists_not, :post_where, :post_where_not, :post_range, :post_exists, :post_exists_not,
253
252
  :post_filter, :post_must, :post_must_not, :post_should, :aggregate, :scroll, :source,
254
253
  :includes, :eager_load, :preload, :sort, :resort, :order, :reorder, :offset, :limit, :paginate,
255
254
  :page, :per, :search, :highlight, :suggest, :custom, :find_in_batches, :find_results_in_batches,
256
255
  :find_each, :find_each_result, :failsafe, :total_entries, :total_count, :timeout, :terminate_after,
257
- :records, :results, :should, :should_not, :must, :must_not, :preference, :search_type, :routing,
256
+ :records, :results, :must, :must_not, :should, :preference, :search_type, :routing,
258
257
  :track_total_hits, :explain
259
258
 
260
259
  # Override to specify the type name used within Elasticsearch. Recap,
@@ -354,6 +353,24 @@ module SearchFlip
354
353
  connection.open_index(index_name_with_prefix)
355
354
  end
356
355
 
356
+ # Freezes the index within Elasticsearch. Raises SearchFlip::ResponseError
357
+ # in case any errors occur.
358
+ #
359
+ # @return [Boolean] Returns true or raises SearchFlip::ResponseError
360
+
361
+ def freeze_index
362
+ connection.freeze_index(index_name_with_prefix)
363
+ end
364
+
365
+ # Unfreezes the index within Elasticsearch. Raises SearchFlip::ResponseError
366
+ # in case any errors occur.
367
+ #
368
+ # @return [Boolean] Returns true or raises SearchFlip::ResponseError
369
+
370
+ def unfreeze_index
371
+ connection.unfreeze_index(index_name_with_prefix)
372
+ end
373
+
357
374
  # Updates the index settings within Elasticsearch according to the index
358
375
  # settings specified. Raises SearchFlip::ResponseError in case any
359
376
  # errors occur.
@@ -481,7 +498,7 @@ module SearchFlip
481
498
  #
482
499
  # @see #index See #index for more details
483
500
 
484
- ruby2_keywords def import(*args)
501
+ def import(*args)
485
502
  index(*args)
486
503
  end
487
504
 
@@ -1,4 +1,3 @@
1
-
2
1
  module SearchFlip
3
2
  class JSON
4
3
  @default_options = {
@@ -15,4 +14,3 @@ module SearchFlip
15
14
  end
16
15
  end
17
16
  end
18
-
@@ -1,4 +1,3 @@
1
-
2
1
  module SearchFlip
3
2
  module Model
4
3
  def self.included(base)
@@ -18,4 +17,3 @@ module SearchFlip
18
17
  end
19
18
  end
20
19
  end
21
-