search_flip 3.9.0 → 4.0.0.beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -16,28 +16,20 @@ module SearchFlip
16
16
  @base_url = options[:base_url] || SearchFlip::Config[:base_url]
17
17
  @http_client = options[:http_client] || SearchFlip::HTTPClient.new
18
18
  @bulk_limit = options[:bulk_limit] || SearchFlip::Config[:bulk_limit]
19
- end
20
-
21
- # Queries and returns the Elasticsearch distribution used.
22
- #
23
- # @example
24
- # connection.distribution # => e.g. "opensearch"
25
- #
26
- # @return [String] The Elasticsearch distribution
27
-
28
- def distribution
29
- @distribution ||= SearchFlip::Config.dig(:version, :distribution) || SearchFlip::JSON.parse(version_response.to_s)["version"]["distribution"]
19
+ @version_mutex = Mutex.new
30
20
  end
31
21
 
32
22
  # Queries and returns the Elasticsearch version used.
33
23
  #
34
24
  # @example
35
- # connection.version # => e.g. "2.4.1"
25
+ # connection.version # => e.g. 2.4.1
36
26
  #
37
27
  # @return [String] The Elasticsearch version
38
28
 
39
29
  def version
40
- @version ||= SearchFlip::Config.dig(:version, :number) || SearchFlip::JSON.parse(version_response.to_s)["version"]["number"]
30
+ @version_mutex.synchronize do
31
+ @version ||= http_client.headers(accept: "application/json").get("#{base_url}/").parse["version"]["number"]
32
+ end
41
33
  end
42
34
 
43
35
  # Queries and returns the Elasticsearch cluster health.
@@ -48,9 +40,7 @@ module SearchFlip
48
40
  # @return [Hash] The raw response
49
41
 
50
42
  def cluster_health
51
- response = http_client.headers(accept: "application/json").get("#{base_url}/_cluster/health")
52
-
53
- SearchFlip::JSON.parse(response.to_s)
43
+ http_client.headers(accept: "application/json").get("#{base_url}/_cluster/health").parse
54
44
  end
55
45
 
56
46
  # Uses the Elasticsearch Multi Search API to execute multiple search requests
@@ -68,7 +58,7 @@ module SearchFlip
68
58
  def msearch(criterias)
69
59
  payload = criterias.flat_map do |criteria|
70
60
  [
71
- SearchFlip::JSON.generate(index: criteria.target.index_name_with_prefix, **(distribution.nil? && version.to_i < 8 ? { type: criteria.target.type_name } : {})),
61
+ SearchFlip::JSON.generate(index: criteria.target.index_name_with_prefix, type: criteria.target.type_name),
72
62
  SearchFlip::JSON.generate(criteria.request)
73
63
  ]
74
64
  end
@@ -100,11 +90,10 @@ module SearchFlip
100
90
  # @return [Hash] The raw response
101
91
 
102
92
  def update_aliases(payload)
103
- response = http_client
93
+ http_client
104
94
  .headers(accept: "application/json", content_type: "application/json")
105
95
  .post("#{base_url}/_aliases", body: SearchFlip::JSON.generate(payload))
106
-
107
- SearchFlip::JSON.parse(response.to_s)
96
+ .parse
108
97
  end
109
98
 
110
99
  # Sends an analyze request to Elasticsearch. Raises
@@ -116,11 +105,10 @@ module SearchFlip
116
105
  # @return [Hash] The raw response
117
106
 
118
107
  def analyze(request, params = {})
119
- response = http_client
108
+ http_client
120
109
  .headers(accept: "application/json")
121
110
  .post("#{base_url}/_analyze", json: request, params: params)
122
-
123
- SearchFlip::JSON.parse(response.to_s)
111
+ .parse
124
112
  end
125
113
 
126
114
  # Fetches information about the specified index aliases. Raises
@@ -136,11 +124,10 @@ module SearchFlip
136
124
  # @return [Hash] The raw response
137
125
 
138
126
  def get_aliases(index_name: "*", alias_name: "*")
139
- response = http_client
127
+ http_client
140
128
  .headers(accept: "application/json", content_type: "application/json")
141
129
  .get("#{base_url}/#{index_name}/_alias/#{alias_name}")
142
-
143
- SearchFlip::JSON.parse(response.to_s)
130
+ .parse
144
131
  end
145
132
 
146
133
  # Returns whether or not the associated Elasticsearch alias already
@@ -172,11 +159,10 @@ module SearchFlip
172
159
  # @return [Array] The raw response
173
160
 
174
161
  def get_indices(name = "*", params: {})
175
- response = http_client
162
+ http_client
176
163
  .headers(accept: "application/json", content_type: "application/json")
177
164
  .get("#{base_url}/_cat/indices/#{name}", params: params)
178
-
179
- SearchFlip::JSON.parse(response.to_s)
165
+ .parse
180
166
  end
181
167
 
182
168
  alias_method :cat_indices, :get_indices
@@ -273,11 +259,10 @@ module SearchFlip
273
259
  # @return [Hash] The index settings
274
260
 
275
261
  def get_index_settings(index_name)
276
- response = http_client
262
+ http_client
277
263
  .headers(accept: "application/json")
278
264
  .get("#{index_url(index_name)}/_settings")
279
-
280
- SearchFlip::JSON.parse(response.to_s)
265
+ .parse
281
266
  end
282
267
 
283
268
  # Sends a refresh request to Elasticsearch. Raises
@@ -287,7 +272,7 @@ module SearchFlip
287
272
  # @return [Boolean] Returns true or raises SearchFlip::ResponseError
288
273
 
289
274
  def refresh(index_names = nil)
290
- http_client.post("#{index_names ? index_url(Array(index_names).join(",")) : base_url}/_refresh")
275
+ http_client.post("#{index_names ? index_url(Array(index_names).join(",")) : base_url}/_refresh", json: {})
291
276
 
292
277
  true
293
278
  end
@@ -304,8 +289,8 @@ module SearchFlip
304
289
  # @return [Boolean] Returns true or raises SearchFlip::ResponseError
305
290
 
306
291
  def update_mapping(index_name, mapping, type_name: nil)
307
- url = type_name && distribution.nil? && version.to_i < 8 ? type_url(index_name, type_name) : index_url(index_name)
308
- params = type_name && distribution.nil? && version.to_f >= 6.7 && version.to_i < 8 ? { include_type_name: true } : {}
292
+ url = type_name ? type_url(index_name, type_name) : index_url(index_name)
293
+ params = type_name && version.to_f >= 6.7 ? { include_type_name: true } : {}
309
294
 
310
295
  http_client.put("#{url}/_mapping", params: params, json: mapping)
311
296
 
@@ -322,12 +307,10 @@ module SearchFlip
322
307
  # @return [Hash] The current type mapping
323
308
 
324
309
  def get_mapping(index_name, type_name: nil)
325
- url = type_name && distribution.nil? && version.to_i < 8 ? type_url(index_name, type_name) : index_url(index_name)
326
- params = type_name && distribution.nil? && version.to_f >= 6.7 && version.to_i < 8 ? { include_type_name: true } : {}
327
-
328
- response = http_client.headers(accept: "application/json").get("#{url}/_mapping", params: params)
310
+ url = type_name ? type_url(index_name, type_name) : index_url(index_name)
311
+ params = type_name && version.to_f >= 6.7 ? { include_type_name: true } : {}
329
312
 
330
- SearchFlip::JSON.parse(response.to_s)
313
+ http_client.headers(accept: "application/json").get("#{url}/_mapping", params: params).parse
331
314
  end
332
315
 
333
316
  # Deletes the specified index from Elasticsearch. Raises
@@ -359,51 +342,6 @@ module SearchFlip
359
342
  raise e
360
343
  end
361
344
 
362
- # Initiates and yields a bulk object, such that index, import, create,
363
- # update and delete requests can be appended to the bulk request. Please
364
- # note that you need to manually pass the desired index name as well as
365
- # type name (depending on the Elasticsearch version) when using #bulk on a
366
- # connection object or Elasticsearch will return an error. After the bulk
367
- # requests are successfully processed all existing indices will
368
- # subsequently be refreshed when auto_refresh is enabled.
369
- #
370
- # @see SearchFlip::Config See SearchFlip::Config for auto_refresh
371
- #
372
- # @example
373
- # connection = SearchFlip::Connection.new
374
- #
375
- # connection.bulk ignore_errors: [409] do |bulk|
376
- # bulk.create comment.id, CommentIndex.serialize(comment),
377
- # _index: CommentIndex.index_name, version: comment.version, version_type: "external_gte"
378
- #
379
- # bulk.delete product.id, _index: ProductIndex.index_name, routing: product.user_id
380
- #
381
- # # ...
382
- # end
383
- #
384
- # @param options [Hash] Specifies options regarding the bulk indexing
385
- # @option options ignore_errors [Array] Specifies an array of http status
386
- # codes that shouldn't raise any exceptions, like eg 409 for conflicts,
387
- # ie when optimistic concurrency control is used.
388
- # @option options raise [Boolean] Prevents any exceptions from being
389
- # raised. Please note that this only applies to the bulk response, not to
390
- # the request in general, such that connection errors, etc will still
391
- # raise.
392
-
393
- def bulk(options = {})
394
- default_options = {
395
- http_client: http_client,
396
- bulk_limit: bulk_limit,
397
- bulk_max_mb: bulk_max_mb
398
- }
399
-
400
- SearchFlip::Bulk.new("#{base_url}/_bulk", default_options.merge(options)) do |indexer|
401
- yield indexer
402
- end
403
-
404
- refresh if SearchFlip::Config[:auto_refresh]
405
- end
406
-
407
345
  # Returns the full Elasticsearch type URL, ie base URL, index name with
408
346
  # prefix and type name.
409
347
  #
@@ -426,11 +364,5 @@ module SearchFlip
426
364
  def index_url(index_name)
427
365
  "#{base_url}/#{index_name}"
428
366
  end
429
-
430
- private
431
-
432
- def version_response
433
- @version_response ||= http_client.headers(accept: "application/json").get("#{base_url}/")
434
- end
435
367
  end
436
368
  end
@@ -26,8 +26,7 @@ module SearchFlip
26
26
 
27
27
  attr_accessor :target, :profile_value, :source_value, :suggest_values, :includes_values,
28
28
  :eager_load_values, :preload_values, :failsafe_value, :scroll_args, :terminate_after_value,
29
- :timeout_value, :preference_value, :search_type_value, :routing_value, :track_total_hits_value,
30
- :http_timeout_value
29
+ :timeout_value, :preference_value, :search_type_value, :routing_value, :track_total_hits_value
31
30
 
32
31
  # Creates a new criteria while merging the attributes (constraints,
33
32
  # settings, etc) of the current criteria with the attributes of another one
@@ -48,7 +47,7 @@ module SearchFlip
48
47
  [
49
48
  :profile_value, :failsafe_value, :terminate_after_value, :timeout_value, :offset_value,
50
49
  :limit_value, :scroll_args, :source_value, :preference_value, :search_type_value,
51
- :routing_value, :track_total_hits_value, :explain_value, :http_timeout_value
50
+ :routing_value, :track_total_hits_value, :explain_value
52
51
  ].each do |name|
53
52
  criteria.send(:"#{name}=", other.send(name)) unless other.send(name).nil?
54
53
  end
@@ -149,22 +148,6 @@ module SearchFlip
149
148
  end
150
149
  end
151
150
 
152
- # Specifies a http timeout, such that a SearchFlip::TimeoutError will be
153
- # thrown when the request times out.
154
- #
155
- # @example
156
- # ProductIndex.http_timeout(3).search("hello world")
157
- #
158
- # @param value [Fixnum] The timeout value
159
- #
160
- # @return [SearchFlip::Criteria] A newly created extended criteria
161
-
162
- def http_timeout(value)
163
- fresh.tap do |criteria|
164
- criteria.http_timeout_value = value
165
- end
166
- end
167
-
168
151
  # Specifies early query termination, such that the processing will be
169
152
  # stopped after the specified number of results has been accumulated.
170
153
  #
@@ -347,15 +330,10 @@ module SearchFlip
347
330
  dupped_request.delete(:from)
348
331
  dupped_request.delete(:size)
349
332
 
350
- http_request = connection.http_client
351
- http_request = http_request.timeout(http_timeout_value) if http_timeout_value
352
-
353
- if connection.distribution || connection.version.to_i >= 5
354
- url = connection.distribution.nil? && connection.version.to_i < 8 ? target.type_url : target.index_url
355
-
356
- http_request.post("#{url}/_delete_by_query", params: request_params.merge(params), json: dupped_request)
333
+ if connection.version.to_i >= 5
334
+ connection.http_client.post("#{target.type_url}/_delete_by_query", params: request_params.merge(params), json: dupped_request)
357
335
  else
358
- http_request.delete("#{target.type_url}/_query", params: request_params.merge(params), json: dupped_request)
336
+ connection.http_client.delete("#{target.type_url}/_query", params: request_params.merge(params), json: dupped_request)
359
337
  end
360
338
 
361
339
  target.refresh if SearchFlip::Config[:auto_refresh]
@@ -523,8 +501,8 @@ module SearchFlip
523
501
  end
524
502
 
525
503
  # Executes the search request for the current criteria, ie sends the
526
- # request to Elasticsearch and returns the response. Connection, timeout
527
- # and response errors will be rescued if you specify the criteria to be
504
+ # request to Elasticsearch and returns the response. Connection and
505
+ # response errors will be rescued if you specify the criteria to be
528
506
  # #failsafe, such that an empty response is returned instead.
529
507
  #
530
508
  # @example
@@ -612,7 +590,6 @@ module SearchFlip
612
590
 
613
591
  def execute!
614
592
  http_request = connection.http_client.headers(accept: "application/json")
615
- http_request = http_request.timeout(http_timeout_value) if http_timeout_value
616
593
 
617
594
  http_response =
618
595
  if scroll_args && scroll_args[:id]
@@ -622,21 +599,17 @@ module SearchFlip
622
599
  json: { scroll: scroll_args[:timeout], scroll_id: scroll_args[:id] }
623
600
  )
624
601
  elsif scroll_args
625
- url = connection.distribution.nil? && connection.version.to_i < 8 ? target.type_url : target.index_url
626
-
627
602
  http_request.post(
628
- "#{url}/_search",
603
+ "#{target.type_url}/_search",
629
604
  params: request_params.merge(scroll: scroll_args[:timeout]),
630
605
  json: request
631
606
  )
632
607
  else
633
- url = connection.distribution.nil? && connection.version.to_i < 8 ? target.type_url : target.index_url
634
-
635
- http_request.post("#{url}/_search", params: request_params, json: request)
608
+ http_request.post("#{target.type_url}/_search", params: request_params, json: request)
636
609
  end
637
610
 
638
611
  SearchFlip::Response.new(self, SearchFlip::JSON.parse(http_response.to_s))
639
- rescue SearchFlip::ConnectionError, SearchFlip::TimeoutError, SearchFlip::ResponseError => e
612
+ rescue SearchFlip::ConnectionError, SearchFlip::ResponseError => e
640
613
  raise e unless failsafe_value
641
614
 
642
615
  SearchFlip::Response.new(self, "took" => 0, "hits" => { "total" => 0, "hits" => [] })
@@ -235,22 +235,6 @@ module SearchFlip
235
235
  filter(match_all: options)
236
236
  end
237
237
 
238
- # Adds a match none filter to the criteria, which simply matches none
239
- # documents at all. Check out the Elasticsearch docs for further details.
240
- #
241
- # @example Basic usage
242
- # CommentIndex.match_none
243
- #
244
- # @example Filter chaining
245
- # query = CommentIndex.search("...")
246
- # query = query.match_none unless current_user.admin?
247
- #
248
- # @return [SearchFlip::Criteria] A newly created extended criteria
249
-
250
- def match_none
251
- filter(match_none: {})
252
- end
253
-
254
238
  # Adds an exists filter to the criteria, which selects all documents for
255
239
  # which the specified field has a non-null value.
256
240
  #
@@ -1,28 +1,7 @@
1
1
  module SearchFlip
2
- # The SearchFlip::HTTPClient class wraps the http gem and responsible for the
3
- # http request/response handling, ie communicating with Elasticsearch. You
4
- # only need to use it directly if you need authentication to communicate with
5
- # Elasticsearch or if you want to set some custom http settings.
6
- #
7
- # @example
8
- # http_client = SearchFlip::HTTPClient.new
9
- #
10
- # # Basic Auth
11
- # http_client = http_client.basic_auth(user: "username", pass: "password")
12
- #
13
- # # Raw Auth Header
14
- # http_client = http_client.auth("Bearer VGhlIEhUVFAgR2VtLCBST0NLUw")
15
- #
16
- # # Proxy Settings
17
- # http_client = http_client.via("proxy.host", 8080)
18
- #
19
- # # Custom headers
20
- # http_client = http_client.headers(key: "value")
21
- #
22
- # # Timeouts
23
- # http_client = http_client.timeout(20)
24
- #
25
- # SearchFlip::Connection.new(base_url: "...", http_client: http_client)
2
+ # The SearchFlip::HTTPClient class wraps the http gem, is for internal use
3
+ # and responsible for the http request/response handling, ie communicating
4
+ # with Elasticsearch.
26
5
 
27
6
  class HTTPClient
28
7
  attr_accessor :request, :plugins
@@ -35,11 +14,11 @@ module SearchFlip
35
14
  class << self
36
15
  extend Forwardable
37
16
 
38
- def_delegators :new, :headers, :via, :basic_auth, :auth, :timeout
17
+ def_delegators :new, :headers, :via, :basic_auth, :auth
39
18
  def_delegators :new, :get, :post, :put, :delete, :head
40
19
  end
41
20
 
42
- [:headers, :via, :basic_auth, :auth, :timeout].each do |method|
21
+ [:headers, :via, :basic_auth, :auth].each do |method|
43
22
  define_method method do |*args|
44
23
  dup.tap do |client|
45
24
  client.request = request.send(method, *args)
@@ -58,32 +37,14 @@ module SearchFlip
58
37
  private
59
38
 
60
39
  def execute(method, uri, options = {})
61
- opts = options.dup
62
- final_request = self
63
-
64
- if opts[:json]
65
- # Manually generate and pass the json body to http-rb to guarantee that
66
- # we have the same json which is used for aws signatures and to
67
- # guarantee that json is always generated as stated in the config
68
-
69
- opts[:body] = JSON.generate(opts.delete(:json))
70
- final_request = final_request.headers(content_type: "application/json")
71
- end
72
-
73
- final_request = plugins.inject(final_request) { |res, cur| cur.call(res, method, uri, opts) }
74
- final_request = final_request.headers({}) # Prevent thread-safety issue of http-rb: https://github.com/httprb/http/issues/558
75
-
76
- response = final_request.request.send(method, uri, opts)
40
+ final_request = plugins.inject(self) { |res, cur| cur.call(res, method, uri, options) }
41
+ response = final_request.request.send(method, uri, options)
77
42
 
78
43
  raise SearchFlip::ResponseError.new(code: response.code, body: response.body.to_s) unless response.status.success?
79
44
 
80
45
  response
81
46
  rescue HTTP::ConnectionError => e
82
47
  raise SearchFlip::ConnectionError, e.message
83
- rescue HTTP::TimeoutError => e
84
- raise SearchFlip::TimeoutError, e.message
85
- rescue HTTP::Error => e
86
- raise SearchFlip::HttpError, e.message
87
48
  end
88
49
  end
89
50
  end
@@ -153,7 +153,7 @@ module SearchFlip
153
153
  # scope to be applied to the scope
154
154
 
155
155
  def each_record(scope, index_scope: false)
156
- return enum_for(:each_record, scope, index_scope: index_scope) unless block_given?
156
+ return enum_for(:each_record, scope) unless block_given?
157
157
 
158
158
  if scope.respond_to?(:find_each)
159
159
  (index_scope ? self.index_scope(scope) : scope).find_each do |record|
@@ -247,14 +247,14 @@ module SearchFlip
247
247
  SearchFlip::Criteria.new(target: self)
248
248
  end
249
249
 
250
- def_delegators :criteria, :all, :profile, :where, :where_not, :filter, :range, :match_all, :match_none,
251
- :exists, :exists_not, :post_where, :post_where_not, :post_range, :post_exists, :post_exists_not,
250
+ def_delegators :criteria, :all, :profile, :where, :where_not, :filter, :range, :match_all, :exists,
251
+ :exists_not, :post_where, :post_where_not, :post_range, :post_exists, :post_exists_not,
252
252
  :post_filter, :post_must, :post_must_not, :post_should, :aggregate, :scroll, :source,
253
253
  :includes, :eager_load, :preload, :sort, :resort, :order, :reorder, :offset, :limit, :paginate,
254
254
  :page, :per, :search, :highlight, :suggest, :custom, :find_in_batches, :find_results_in_batches,
255
255
  :find_each, :find_each_result, :failsafe, :total_entries, :total_count, :timeout, :terminate_after,
256
256
  :records, :results, :must, :must_not, :should, :preference, :search_type, :routing,
257
- :track_total_hits, :explain, :http_timeout
257
+ :track_total_hits, :explain
258
258
 
259
259
  # Override to specify the type name used within Elasticsearch. Recap,
260
260
  # this gem uses an individual index for each index class, because
@@ -438,7 +438,7 @@ module SearchFlip
438
438
  # equal to _doc.
439
439
 
440
440
  def include_type_name?
441
- type_name != "_doc" || (connection.distribution.nil? && connection.version.to_i < 7)
441
+ type_name != "_doc" || connection.version.to_i < 7
442
442
  end
443
443
 
444
444
  # Retrieves the document specified by id from Elasticsearch. Raises
@@ -455,8 +455,7 @@ module SearchFlip
455
455
  # @return [Hash] The specified document
456
456
 
457
457
  def get(id, params = {})
458
- url = connection.distribution.nil? && connection.version.to_i < 8 ? type_url : "#{index_url}/_doc"
459
- response = connection.http_client.headers(accept: "application/json").get("#{url}/#{id}", params: params)
458
+ response = connection.http_client.headers(accept: "application/json").get("#{type_url}/#{id}", params: params)
460
459
 
461
460
  SearchFlip::JSON.parse(response.to_s)
462
461
  end
@@ -474,8 +473,7 @@ module SearchFlip
474
473
  # @return [Hash] The raw response
475
474
 
476
475
  def mget(request, params = {})
477
- url = connection.distribution.nil? && connection.version.to_i < 8 ? type_url : index_url
478
- response = connection.http_client.headers(accept: "application/json").post("#{url}/_mget", json: request, params: params)
476
+ response = connection.http_client.headers(accept: "application/json").post("#{type_url}/_mget", json: request, params: params)
479
477
 
480
478
  SearchFlip::JSON.parse(response.to_s)
481
479
  end
@@ -489,9 +487,7 @@ module SearchFlip
489
487
  # @return [Hash] The raw response
490
488
 
491
489
  def analyze(request, params = {})
492
- response = connection.http_client.headers(accept: "application/json").post("#{index_url}/_analyze", json: request, params: params)
493
-
494
- SearchFlip::JSON.parse(response.to_s)
490
+ connection.http_client.headers(accept: "application/json").post("#{index_url}/_analyze", json: request, params: params).parse
495
491
  end
496
492
 
497
493
  # Sends a index refresh request to Elasticsearch. Raises
@@ -601,7 +597,7 @@ module SearchFlip
601
597
  scope
602
598
  end
603
599
 
604
- # Initiates and yields a bulk object, such that index, import, create,
600
+ # Initiates and yields the bulk object, such that index, import, create,
605
601
  # update and delete requests can be appended to the bulk request. Sends a
606
602
  # refresh request afterwards if auto_refresh is enabled.
607
603
  #
@@ -633,9 +629,7 @@ module SearchFlip
633
629
  bulk_max_mb: connection.bulk_max_mb
634
630
  }
635
631
 
636
- url = connection.distribution.nil? && connection.version.to_i < 8 ? type_url : index_url
637
-
638
- SearchFlip::Bulk.new("#{url}/_bulk", default_options.merge(options)) do |indexer|
632
+ SearchFlip::Bulk.new("#{type_url}/_bulk", default_options.merge(options)) do |indexer|
639
633
  yield indexer
640
634
  end
641
635
 
@@ -1,11 +1,11 @@
1
1
  module SearchFlip
2
2
  class JSON
3
3
  def self.generate(obj)
4
- Oj.dump(obj, SearchFlip::Config[:json_options])
4
+ Oj.dump(obj, mode: :custom, use_to_json: true)
5
5
  end
6
6
 
7
- def self.parse(json)
8
- ::JSON.parse(json)
7
+ def self.parse(str)
8
+ Oj.load(str)
9
9
  end
10
10
  end
11
11
  end
@@ -156,8 +156,7 @@ module SearchFlip
156
156
  end
157
157
 
158
158
  # Returns the results, ie hits, wrapped in a SearchFlip::Result object
159
- # which basically is a Hashie::Mash. Check out the Hashie docs for further
160
- # details.
159
+ # which basically is a Hash with method-like access.
161
160
  #
162
161
  # @example
163
162
  # CommentIndex.search("hello world").results
@@ -166,7 +165,7 @@ module SearchFlip
166
165
  # @return [Array] An array of results
167
166
 
168
167
  def results
169
- @results ||= hits["hits"].map { |hit| Result.from_hit(hit) }
168
+ @results ||= hits["hits"].map { |hit| SearchFlip::Result.from_hit(hit) }
170
169
  end
171
170
 
172
171
  # Returns the named sugggetion, if a name is specified or alle suggestions.
@@ -224,7 +223,7 @@ module SearchFlip
224
223
 
225
224
  def records
226
225
  @records ||= begin
227
- sort_map = ids.each_with_index.with_object({}) { |(id, index), hash| hash[id.to_s] = index }
226
+ sort_map = ids.each_with_index.each_with_object({}) { |(id, index), hash| hash[id.to_s] = index }
228
227
 
229
228
  scope.to_a.sort_by { |record| sort_map[criteria.target.record_id(record).to_s] }
230
229
  end
@@ -304,13 +303,13 @@ module SearchFlip
304
303
 
305
304
  @aggregations[key] =
306
305
  if response["aggregations"].nil? || response["aggregations"][key].nil?
307
- Result.new
306
+ SearchFlip::Result.new
308
307
  elsif response["aggregations"][key]["buckets"].is_a?(Array)
309
- response["aggregations"][key]["buckets"].each_with_object({}) { |bucket, hash| hash[bucket["key"]] = Result.new(bucket) }
308
+ response["aggregations"][key]["buckets"].each_with_object({}) { |bucket, hash| hash[bucket["key"]] = SearchFlip::Result.convert(bucket) }
310
309
  elsif response["aggregations"][key]["buckets"].is_a?(Hash)
311
- Result.new response["aggregations"][key]["buckets"]
310
+ SearchFlip::Result.convert(response["aggregations"][key]["buckets"])
312
311
  else
313
- Result.new response["aggregations"][key]
312
+ SearchFlip::Result.convert(response["aggregations"][key])
314
313
  end
315
314
  end
316
315
  end
@@ -1,29 +1,55 @@
1
1
  module SearchFlip
2
- # The SearchFlip::Result class basically is a hash wrapper that uses
3
- # Hashie::Mash to provide convenient method access to the hash attributes.
2
+ # The SearchFlip::Result class is a simple Hash, but extended with
3
+ # method-like access. Keys assigned via methods are stored as strings.
4
+ #
5
+ # @example method access
6
+ # result = SearchFlip::Result.new
7
+ # result["some_key"] = "value"
8
+ # result.some_key # => "value"
4
9
 
5
- class Result < Hashie::Mash
6
- def self.disable_warnings?(*args)
7
- true
8
- end
10
+ class Result < Hash
11
+ def self.convert(hash)
12
+ res = self[hash]
9
13
 
10
- # Creates a SearchFlip::Result object from a raw hit. Useful for e.g.
11
- # top hits aggregations.
12
- #
13
- # @example
14
- # query = ProductIndex.aggregate(top_sales: { top_hits: "..." })
15
- # top_sales_hits = query.aggregations(:top_sales).top_hits.hits.hits
16
- #
17
- # SearchFlip::Result.from_hit(top_sales_hits.first)
14
+ res.each do |key, value|
15
+ if value.is_a?(Hash)
16
+ res[key] = convert(value)
17
+ elsif value.is_a?(Array)
18
+ res[key] = convert_array(value)
19
+ end
20
+ end
18
21
 
19
- def self.from_hit(hit)
20
- raw_result = (hit["_source"] || {}).dup
22
+ res
23
+ end
21
24
 
22
- raw_result["_hit"] = hit.each_with_object({}) do |(key, value), hash|
23
- hash[key] = value if key != "_source"
25
+ def self.convert_array(arr)
26
+ arr.map do |obj|
27
+ if obj.is_a?(Hash)
28
+ convert(obj)
29
+ elsif obj.is_a?(Array)
30
+ convert_array(obj)
31
+ else
32
+ obj
33
+ end
24
34
  end
35
+ end
36
+
37
+ # rubocop:disable Lint/MissingSuper
25
38
 
26
- new(raw_result)
39
+ def method_missing(name, *args, &block)
40
+ self[name.to_s]
41
+ end
42
+
43
+ # rubocop:enable Lint/MissingSuper
44
+
45
+ def respond_to_missing?(name, include_private = false)
46
+ key?(name.to_s) || super
47
+ end
48
+
49
+ def self.from_hit(hit)
50
+ res = convert(hit["_source"] || {})
51
+ res["_hit"] = convert(self[hit].tap { |hash| hash.delete("_source") })
52
+ res
27
53
  end
28
54
  end
29
55
  end