searchkick 4.4.0 → 5.3.1

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.
@@ -1,10 +1,9 @@
1
- require "forwardable"
2
-
3
1
  module Searchkick
4
2
  class Results
5
3
  include Enumerable
6
4
  extend Forwardable
7
5
 
6
+ # TODO remove klass and options in 6.0
8
7
  attr_reader :klass, :response, :options
9
8
 
10
9
  def_delegators :results, :each, :any?, :empty?, :size, :length, :slice, :[], :to_ary
@@ -15,82 +14,23 @@ module Searchkick
15
14
  @options = options
16
15
  end
17
16
 
17
+ # TODO make private in 6.0
18
18
  def results
19
19
  @results ||= with_hit.map(&:first)
20
20
  end
21
21
 
22
22
  def with_hit
23
- @with_hit ||= begin
24
- if options[:load]
25
- # results can have different types
26
- results = {}
27
-
28
- hits.group_by { |hit, _| hit["_index"] }.each do |index, grouped_hits|
29
- klasses =
30
- if @klass
31
- [@klass]
32
- else
33
- index_alias = index.split("_")[0..-2].join("_")
34
- Array((options[:index_mapping] || {})[index_alias])
35
- end
36
- raise Searchkick::Error, "Unknown model for index: #{index}" unless klasses.any?
37
-
38
- results[index] = {}
39
- klasses.each do |klass|
40
- results[index].merge!(results_query(klass, grouped_hits).to_a.index_by { |r| r.id.to_s })
41
- end
42
- end
43
-
44
- missing_ids = []
45
-
46
- # sort
47
- results =
48
- hits.map do |hit|
49
- result = results[hit["_index"]][hit["_id"].to_s]
50
- if result && !(options[:load].is_a?(Hash) && options[:load][:dumpable])
51
- if (hit["highlight"] || options[:highlight]) && !result.respond_to?(:search_highlights)
52
- highlights = hit_highlights(hit)
53
- result.define_singleton_method(:search_highlights) do
54
- highlights
55
- end
56
- end
57
- end
58
- [result, hit]
59
- end.select do |result, hit|
60
- missing_ids << hit["_id"] unless result
61
- result
62
- end
63
-
64
- if missing_ids.any?
65
- Searchkick.warn("Records in search index do not exist in database: #{missing_ids.join(", ")}")
66
- end
67
-
68
- results
69
- else
70
- hits.map do |hit|
71
- result =
72
- if hit["_source"]
73
- hit.except("_source").merge(hit["_source"])
74
- elsif hit["fields"]
75
- hit.except("fields").merge(hit["fields"])
76
- else
77
- hit
78
- end
79
-
80
- if hit["highlight"] || options[:highlight]
81
- highlight = Hash[hit["highlight"].to_a.map { |k, v| [base_field(k), v.first] }]
82
- options[:highlighted_fields].map { |k| base_field(k) }.each do |k|
83
- result["highlighted_#{k}"] ||= (highlight[k] || result[k])
84
- end
85
- end
23
+ return enum_for(:with_hit) unless block_given?
86
24
 
87
- result["id"] ||= result["_id"] # needed for legacy reasons
88
- [HashWrapper.new(result), hit]
89
- end
90
- end
25
+ build_hits.each do |result|
26
+ yield result
91
27
  end
92
28
  end
93
29
 
30
+ def missing_records
31
+ @missing_records ||= with_hit_and_missing_records[1]
32
+ end
33
+
94
34
  def suggestions
95
35
  if response["suggest"]
96
36
  response["suggest"].values.flat_map { |v| v.first["options"] }.sort_by { |o| -o["score"] }.map { |o| o["text"] }.uniq
@@ -129,7 +69,11 @@ module Searchkick
129
69
  end
130
70
 
131
71
  def model_name
132
- klass.model_name
72
+ if klass.nil?
73
+ ActiveModel::Name.new(self.class, nil, 'Result')
74
+ else
75
+ klass.model_name
76
+ end
133
77
  end
134
78
 
135
79
  def entry_name(options = {})
@@ -199,7 +143,7 @@ module Searchkick
199
143
 
200
144
  def hits
201
145
  if error
202
- raise Searchkick::Error, "Query error - use the error method to view it"
146
+ raise Error, "Query error - use the error method to view it"
203
147
  else
204
148
  @response["hits"]["hits"]
205
149
  end
@@ -212,8 +156,18 @@ module Searchkick
212
156
  end
213
157
 
214
158
  def with_highlights(multiple: false)
215
- with_hit.map do |result, hit|
216
- [result, hit_highlights(hit, multiple: multiple)]
159
+ return enum_for(:with_highlights, multiple: multiple) unless block_given?
160
+
161
+ with_hit.each do |result, hit|
162
+ yield result, hit_highlights(hit, multiple: multiple)
163
+ end
164
+ end
165
+
166
+ def with_score
167
+ return enum_for(:with_score) unless block_given?
168
+
169
+ with_hit.each do |result, hit|
170
+ yield result, hit["_score"]
217
171
  end
218
172
  end
219
173
 
@@ -226,7 +180,7 @@ module Searchkick
226
180
  end
227
181
 
228
182
  def scroll
229
- raise Searchkick::Error, "Pass `scroll` option to the search method for scrolling" unless scroll_id
183
+ raise Error, "Pass `scroll` option to the search method for scrolling" unless scroll_id
230
184
 
231
185
  if block_given?
232
186
  records = self
@@ -237,17 +191,12 @@ module Searchkick
237
191
 
238
192
  records.clear_scroll
239
193
  else
240
- params = {
241
- scroll: options[:scroll],
242
- scroll_id: scroll_id
243
- }
244
-
245
194
  begin
246
195
  # TODO Active Support notifications for this scroll call
247
- Searchkick::Results.new(@klass, Searchkick.client.scroll(params), @options)
248
- rescue Elasticsearch::Transport::Transport::Errors::NotFound => e
249
- if e.class.to_s =~ /NotFound/ && e.message =~ /search_context_missing_exception/i
250
- raise Searchkick::Error, "Scroll id has expired"
196
+ Results.new(@klass, Searchkick.client.scroll(scroll: options[:scroll], body: {scroll_id: scroll_id}), @options)
197
+ rescue => e
198
+ if Searchkick.not_found_error?(e) && e.message =~ /search_context_missing_exception/i
199
+ raise Error, "Scroll id has expired"
251
200
  else
252
201
  raise e
253
202
  end
@@ -261,30 +210,116 @@ module Searchkick
261
210
  # not required as scroll will expire
262
211
  # but there is a cost to open scrolls
263
212
  Searchkick.client.clear_scroll(scroll_id: scroll_id)
264
- rescue Elasticsearch::Transport::Transport::Error
265
- # do nothing
213
+ rescue => e
214
+ raise e unless Searchkick.transport_error?(e)
266
215
  end
267
216
  end
268
217
 
269
218
  private
270
219
 
220
+ def with_hit_and_missing_records
221
+ @with_hit_and_missing_records ||= begin
222
+ missing_records = []
223
+
224
+ if options[:load]
225
+ grouped_hits = hits.group_by { |hit, _| hit["_index"] }
226
+
227
+ # determine models
228
+ index_models = {}
229
+ grouped_hits.each do |index, _|
230
+ models =
231
+ if @klass
232
+ [@klass]
233
+ else
234
+ index_alias = index.split("_")[0..-2].join("_")
235
+ Array((options[:index_mapping] || {})[index_alias])
236
+ end
237
+ raise Error, "Unknown model for index: #{index}. Pass the `models` option to the search method." unless models.any?
238
+ index_models[index] = models
239
+ end
240
+
241
+ # fetch results
242
+ results = {}
243
+ grouped_hits.each do |index, index_hits|
244
+ results[index] = {}
245
+ index_models[index].each do |model|
246
+ results[index].merge!(results_query(model, index_hits).to_a.index_by { |r| r.id.to_s })
247
+ end
248
+ end
249
+
250
+ # sort
251
+ results =
252
+ hits.map do |hit|
253
+ result = results[hit["_index"]][hit["_id"].to_s]
254
+ if result && !(options[:load].is_a?(Hash) && options[:load][:dumpable])
255
+ if (hit["highlight"] || options[:highlight]) && !result.respond_to?(:search_highlights)
256
+ highlights = hit_highlights(hit)
257
+ result.define_singleton_method(:search_highlights) do
258
+ highlights
259
+ end
260
+ end
261
+ end
262
+ [result, hit]
263
+ end.select do |result, hit|
264
+ unless result
265
+ models = index_models[hit["_index"]]
266
+ missing_records << {
267
+ id: hit["_id"],
268
+ # may be multiple models for inheritance with child models
269
+ # not ideal to return different types
270
+ # but this situation shouldn't be common
271
+ model: models.size == 1 ? models.first : models
272
+ }
273
+ end
274
+ result
275
+ end
276
+ else
277
+ results =
278
+ hits.map do |hit|
279
+ result =
280
+ if hit["_source"]
281
+ hit.except("_source").merge(hit["_source"])
282
+ elsif hit["fields"]
283
+ hit.except("fields").merge(hit["fields"])
284
+ else
285
+ hit
286
+ end
287
+
288
+ if hit["highlight"] || options[:highlight]
289
+ highlight = hit["highlight"].to_a.to_h { |k, v| [base_field(k), v.first] }
290
+ options[:highlighted_fields].map { |k| base_field(k) }.each do |k|
291
+ result["highlighted_#{k}"] ||= (highlight[k] || result[k])
292
+ end
293
+ end
294
+
295
+ result["id"] ||= result["_id"] # needed for legacy reasons
296
+ [HashWrapper.new(result), hit]
297
+ end
298
+ end
299
+
300
+ [results, missing_records]
301
+ end
302
+ end
303
+
304
+ def build_hits
305
+ @build_hits ||= begin
306
+ if missing_records.any?
307
+ Searchkick.warn("Records in search index do not exist in database: #{missing_records.map { |v| "#{Array(v[:model]).map(&:model_name).sort.join("/")} #{v[:id]}" }.join(", ")}")
308
+ end
309
+ with_hit_and_missing_records[0]
310
+ end
311
+ end
312
+
271
313
  def results_query(records, hits)
314
+ records = Searchkick.scope(records)
315
+
272
316
  ids = hits.map { |hit| hit["_id"] }
273
317
  if options[:includes] || options[:model_includes]
274
318
  included_relations = []
275
319
  combine_includes(included_relations, options[:includes])
276
320
  combine_includes(included_relations, options[:model_includes][records]) if options[:model_includes]
277
321
 
278
- records =
279
- if defined?(NoBrainer::Document) && records < NoBrainer::Document
280
- if Gem.loaded_specs["nobrainer"].version >= Gem::Version.new("0.21")
281
- records.eager_load(included_relations)
282
- else
283
- records.preload(included_relations)
284
- end
285
- else
286
- records.includes(included_relations)
287
- end
322
+ records = records.includes(included_relations)
288
323
  end
289
324
 
290
325
  if options[:scope_results]
@@ -310,7 +345,7 @@ module Searchkick
310
345
 
311
346
  def hit_highlights(hit, multiple: false)
312
347
  if hit["highlight"]
313
- Hash[hit["highlight"].map { |k, v| [(options[:json] ? k : k.sub(/\.#{@options[:match_suffix]}\z/, "")).to_sym, multiple ? v : v.first] }]
348
+ hit["highlight"].to_h { |k, v| [(options[:json] ? k : k.sub(/\.#{@options[:match_suffix]}\z/, "")).to_sym, multiple ? v : v.first] }
314
349
  else
315
350
  {}
316
351
  end
@@ -1,3 +1,3 @@
1
1
  module Searchkick
2
- VERSION = "4.4.0"
2
+ VERSION = "5.3.1"
3
3
  end
@@ -0,0 +1,11 @@
1
+ module Searchkick
2
+ class Where
3
+ def initialize(relation)
4
+ @relation = relation
5
+ end
6
+
7
+ def not(value)
8
+ @relation.where(_not: value)
9
+ end
10
+ end
11
+ end