searchkick 4.6.3 → 5.0.2

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.
@@ -2,16 +2,20 @@ module Searchkick
2
2
  class BulkReindexJob < ActiveJob::Base
3
3
  queue_as { Searchkick.queue_name }
4
4
 
5
+ # TODO remove min_id and max_id in Searchkick 6
5
6
  def perform(class_name:, record_ids: nil, index_name: nil, method_name: nil, batch_id: nil, min_id: nil, max_id: nil)
6
- klass = class_name.constantize
7
- index = index_name ? Searchkick::Index.new(index_name, **klass.searchkick_options) : klass.searchkick_index
7
+ model = Searchkick.load_model(class_name)
8
+ index = model.searchkick_index(name: index_name)
9
+
10
+ # legacy
8
11
  record_ids ||= min_id..max_id
9
- index.import_scope(
10
- Searchkick.load_records(klass, record_ids),
11
- method_name: method_name,
12
- batch: true,
13
- batch_id: batch_id
14
- )
12
+
13
+ relation = Searchkick.scope(model)
14
+ relation = Searchkick.load_records(relation, record_ids)
15
+ relation = relation.search_import if relation.respond_to?(:search_import)
16
+
17
+ RecordIndexer.new(index).reindex(relation, mode: :inline, method_name: method_name, full: false)
18
+ RelationIndexer.new(index).batch_completed(batch_id) if batch_id
15
19
  end
16
20
  end
17
21
  end
@@ -0,0 +1,40 @@
1
+ # based on https://gist.github.com/mnutt/566725
2
+ module Searchkick
3
+ module ControllerRuntime
4
+ extend ActiveSupport::Concern
5
+
6
+ protected
7
+
8
+ attr_internal :searchkick_runtime
9
+
10
+ def process_action(action, *args)
11
+ # We also need to reset the runtime before each action
12
+ # because of queries in middleware or in cases we are streaming
13
+ # and it won't be cleaned up by the method below.
14
+ Searchkick::LogSubscriber.reset_runtime
15
+ super
16
+ end
17
+
18
+ def cleanup_view_runtime
19
+ searchkick_rt_before_render = Searchkick::LogSubscriber.reset_runtime
20
+ runtime = super
21
+ searchkick_rt_after_render = Searchkick::LogSubscriber.reset_runtime
22
+ self.searchkick_runtime = searchkick_rt_before_render + searchkick_rt_after_render
23
+ runtime - searchkick_rt_after_render
24
+ end
25
+
26
+ def append_info_to_payload(payload)
27
+ super
28
+ payload[:searchkick_runtime] = (searchkick_runtime || 0) + Searchkick::LogSubscriber.reset_runtime
29
+ end
30
+
31
+ module ClassMethods
32
+ def log_process_action(payload)
33
+ messages = super
34
+ runtime = payload[:searchkick_runtime]
35
+ messages << ("Searchkick: %.1fms" % runtime.to_f) if runtime.to_f > 0
36
+ messages
37
+ end
38
+ end
39
+ end
40
+ end
@@ -1,5 +1,3 @@
1
- require "searchkick/index_options"
2
-
3
1
  module Searchkick
4
2
  class Index
5
3
  attr_reader :name, :options
@@ -40,12 +38,15 @@ module Searchkick
40
38
  client.indices.exists_alias name: name
41
39
  end
42
40
 
41
+ # call to_h for consistent results between elasticsearch gem 7 and 8
42
+ # could do for all API calls, but just do for ones where return value is focus for now
43
43
  def mapping
44
- client.indices.get_mapping index: name
44
+ client.indices.get_mapping(index: name).to_h
45
45
  end
46
46
 
47
+ # call to_h for consistent results between elasticsearch gem 7 and 8
47
48
  def settings
48
- client.indices.get_settings index: name
49
+ client.indices.get_settings(index: name).to_h
49
50
  end
50
51
 
51
52
  def refresh_interval
@@ -70,12 +71,12 @@ module Searchkick
70
71
  }
71
72
  )
72
73
 
73
- Searchkick::Results.new(nil, response).total_count
74
+ Results.new(nil, response).total_count
74
75
  end
75
76
 
76
77
  def promote(new_name, update_refresh_interval: false)
77
78
  if update_refresh_interval
78
- new_index = Searchkick::Index.new(new_name, @options)
79
+ new_index = Index.new(new_name, @options)
79
80
  settings = options[:settings] || {}
80
81
  refresh_interval = (settings[:index] && settings[:index][:refresh_interval]) || "1s"
81
82
  new_index.update_settings(index: {refresh_interval: refresh_interval})
@@ -97,7 +98,7 @@ module Searchkick
97
98
  record_data = RecordData.new(self, record).record_data
98
99
 
99
100
  # remove underscore
100
- get_options = Hash[record_data.map { |k, v| [k.to_s.sub(/\A_/, "").to_sym, v] }]
101
+ get_options = record_data.to_h { |k, v| [k.to_s.sub(/\A_/, "").to_sym, v] }
101
102
 
102
103
  client.get(get_options)["_source"]
103
104
  end
@@ -122,37 +123,52 @@ module Searchkick
122
123
  def clean_indices
123
124
  indices = all_indices(unaliased: true)
124
125
  indices.each do |index|
125
- Searchkick::Index.new(index).delete
126
+ Index.new(index).delete
126
127
  end
127
128
  indices
128
129
  end
129
130
 
130
- # record based
131
- # use helpers for notifications
132
-
133
131
  def store(record)
134
- bulk_indexer.bulk_index([record])
132
+ notify(record, "Store") do
133
+ queue_index([record])
134
+ end
135
135
  end
136
136
 
137
137
  def remove(record)
138
- bulk_indexer.bulk_delete([record])
138
+ notify(record, "Remove") do
139
+ queue_delete([record])
140
+ end
139
141
  end
140
142
 
141
143
  def update_record(record, method_name)
142
- bulk_indexer.bulk_update([record], method_name)
144
+ notify(record, "Update") do
145
+ queue_update([record], method_name)
146
+ end
143
147
  end
144
148
 
145
149
  def bulk_delete(records)
146
- bulk_indexer.bulk_delete(records)
150
+ return if records.empty?
151
+
152
+ notify_bulk(records, "Delete") do
153
+ queue_delete(records)
154
+ end
147
155
  end
148
156
 
149
157
  def bulk_index(records)
150
- bulk_indexer.bulk_index(records)
158
+ return if records.empty?
159
+
160
+ notify_bulk(records, "Import") do
161
+ queue_index(records)
162
+ end
151
163
  end
152
164
  alias_method :import, :bulk_index
153
165
 
154
166
  def bulk_update(records, method_name)
155
- bulk_indexer.bulk_update(records, method_name)
167
+ return if records.empty?
168
+
169
+ notify_bulk(records, "Update") do
170
+ queue_update(records, method_name)
171
+ end
156
172
  end
157
173
 
158
174
  def search_id(record)
@@ -163,20 +179,12 @@ module Searchkick
163
179
  RecordData.new(self, record).document_type
164
180
  end
165
181
 
166
- # TODO use like: [{_index: ..., _id: ...}] in Searchkick 5
167
182
  def similar_record(record, **options)
168
- like_text = retrieve(record).to_hash
169
- .keep_if { |k, _| !options[:fields] || options[:fields].map(&:to_s).include?(k) }
170
- .values.compact.join(" ")
171
-
172
- options[:where] ||= {}
173
- options[:where][:_id] ||= {}
174
- options[:where][:_id][:not] = Array(options[:where][:_id][:not]) + [record.id.to_s]
175
183
  options[:per_page] ||= 10
176
- options[:similar] = true
184
+ options[:similar] = [RecordData.new(self, record).record_data]
185
+ options[:models] ||= [record.class] unless options.key?(:model)
177
186
 
178
- # TODO use index class instead of record class
179
- Searchkick.search(like_text, model: record.class, **options)
187
+ Searchkick.search("*", **options)
180
188
  end
181
189
 
182
190
  def reload_synonyms
@@ -186,8 +194,9 @@ module Searchkick
186
194
  raise Error, "Requires Elasticsearch 7.3+" if Searchkick.server_below?("7.3.0")
187
195
  begin
188
196
  client.transport.perform_request("GET", "#{CGI.escape(name)}/_reload_search_analyzers")
189
- rescue Elasticsearch::Transport::Transport::Errors::MethodNotAllowed
190
- raise Error, "Requires non-OSS version of Elasticsearch"
197
+ rescue => e
198
+ raise Error, "Requires non-OSS version of Elasticsearch" if Searchkick.not_allowed_error?(e)
199
+ raise e
191
200
  end
192
201
  end
193
202
  end
@@ -195,54 +204,72 @@ module Searchkick
195
204
  # queue
196
205
 
197
206
  def reindex_queue
198
- Searchkick::ReindexQueue.new(name)
207
+ ReindexQueue.new(name)
199
208
  end
200
209
 
201
210
  # reindex
202
211
 
203
- def reindex(relation, method_name, scoped:, full: false, scope: nil, **options)
212
+ # note: this is designed to be used internally
213
+ # so it does not check object matches index class
214
+ def reindex(object, method_name: nil, full: false, **options)
215
+ if object.is_a?(Array)
216
+ # note: purposefully skip full
217
+ return reindex_records(object, method_name: method_name, **options)
218
+ end
219
+
220
+ if !object.respond_to?(:searchkick_klass)
221
+ raise Error, "Cannot reindex object"
222
+ end
223
+
224
+ scoped = Searchkick.relation?(object)
225
+ # call searchkick_klass for inheritance
226
+ relation = scoped ? object.all : Searchkick.scope(object.searchkick_klass).all
227
+
204
228
  refresh = options.fetch(:refresh, !scoped)
205
229
  options.delete(:refresh)
206
230
 
207
- if method_name
208
- # TODO throw ArgumentError
209
- Searchkick.warn("unsupported keywords: #{options.keys.map(&:inspect).join(", ")}") if options.any?
231
+ if method_name || (scoped && !full)
232
+ mode = options.delete(:mode) || :inline
233
+ raise ArgumentError, "unsupported keywords: #{options.keys.map(&:inspect).join(", ")}" if options.any?
210
234
 
211
- # update
212
- import_scope(relation, method_name: method_name, scope: scope)
213
- self.refresh if refresh
214
- true
215
- elsif scoped && !full
216
- # TODO throw ArgumentError
217
- Searchkick.warn("unsupported keywords: #{options.keys.map(&:inspect).join(", ")}") if options.any?
218
-
219
- # reindex association
220
- import_scope(relation, scope: scope)
235
+ # import only
236
+ import_scope(relation, method_name: method_name, mode: mode)
221
237
  self.refresh if refresh
222
238
  true
223
239
  else
224
- # full reindex
225
- reindex_scope(relation, scope: scope, **options)
240
+ async = options.delete(:async)
241
+ if async
242
+ if async.is_a?(Hash) && async[:wait]
243
+ # TODO warn in 5.1
244
+ # Searchkick.warn "async option is deprecated - use mode: :async, wait: true instead"
245
+ options[:wait] = true unless options.key?(:wait)
246
+ else
247
+ # TODO warn in 5.1
248
+ # Searchkick.warn "async option is deprecated - use mode: :async instead"
249
+ end
250
+ options[:mode] ||= :async
251
+ end
252
+
253
+ full_reindex(relation, **options)
226
254
  end
227
255
  end
228
256
 
229
257
  def create_index(index_options: nil)
230
258
  index_options ||= self.index_options
231
- index = Searchkick::Index.new("#{name}_#{Time.now.strftime('%Y%m%d%H%M%S%L')}", @options)
259
+ index = Index.new("#{name}_#{Time.now.strftime('%Y%m%d%H%M%S%L')}", @options)
232
260
  index.create(index_options)
233
261
  index
234
262
  end
235
263
 
236
264
  def import_scope(relation, **options)
237
- bulk_indexer.import_scope(relation, **options)
265
+ relation_indexer.reindex(relation, **options)
238
266
  end
239
267
 
240
268
  def batches_left
241
- bulk_indexer.batches_left
269
+ relation_indexer.batches_left
242
270
  end
243
271
 
244
- # other
245
-
272
+ # private
246
273
  def klass_document_type(klass, ignore_type = false)
247
274
  @klass_document_type[[klass, ignore_type]] ||= begin
248
275
  if !ignore_type && klass.searchkick_klass.searchkick_options[:_type]
@@ -255,7 +282,7 @@ module Searchkick
255
282
  end
256
283
  end
257
284
 
258
- # should not be public
285
+ # private
259
286
  def conversions_fields
260
287
  @conversions_fields ||= begin
261
288
  conversions = Array(options[:conversions])
@@ -263,10 +290,12 @@ module Searchkick
263
290
  end
264
291
  end
265
292
 
293
+ # private
266
294
  def suggest_fields
267
295
  @suggest_fields ||= Array(options[:suggest]).map(&:to_s)
268
296
  end
269
297
 
298
+ # private
270
299
  def locations_fields
271
300
  @locations_fields ||= begin
272
301
  locations = Array(options[:locations])
@@ -285,8 +314,20 @@ module Searchkick
285
314
  Searchkick.client
286
315
  end
287
316
 
288
- def bulk_indexer
289
- @bulk_indexer ||= BulkIndexer.new(self)
317
+ def queue_index(records)
318
+ Searchkick.indexer.queue(records.map { |r| RecordData.new(self, r).index_data })
319
+ end
320
+
321
+ def queue_delete(records)
322
+ Searchkick.indexer.queue(records.reject { |r| r.id.blank? }.map { |r| RecordData.new(self, r).delete_data })
323
+ end
324
+
325
+ def queue_update(records, method_name)
326
+ Searchkick.indexer.queue(records.map { |r| RecordData.new(self, r).update_data(method_name) })
327
+ end
328
+
329
+ def relation_indexer
330
+ @relation_indexer ||= RelationIndexer.new(self)
290
331
  end
291
332
 
292
333
  def index_settings
@@ -297,13 +338,24 @@ module Searchkick
297
338
  index.import_scope(relation, **import_options)
298
339
  end
299
340
 
341
+ def reindex_records(object, mode: nil, refresh: false, **options)
342
+ mode ||= Searchkick.callbacks_value || @options[:callbacks] || :inline
343
+ mode = :inline if mode == :bulk
344
+
345
+ result = RecordIndexer.new(self).reindex(object, mode: mode, full: false, **options)
346
+ self.refresh if refresh
347
+ result
348
+ end
349
+
300
350
  # https://gist.github.com/jarosan/3124884
301
351
  # http://www.elasticsearch.org/blog/changing-mapping-with-zero-downtime/
302
- def reindex_scope(relation, import: true, resume: false, retain: false, async: false, refresh_interval: nil, scope: nil)
352
+ def full_reindex(relation, import: true, resume: false, retain: false, mode: nil, refresh_interval: nil, scope: nil, wait: nil)
353
+ raise ArgumentError, "wait only available in :async mode" if !wait.nil? && mode != :async
354
+
303
355
  if resume
304
356
  index_name = all_indices.sort.last
305
- raise Searchkick::Error, "No index to resume" unless index_name
306
- index = Searchkick::Index.new(index_name, @options)
357
+ raise Error, "No index to resume" unless index_name
358
+ index = Index.new(index_name, @options)
307
359
  else
308
360
  clean_indices unless retain
309
361
 
@@ -313,9 +365,9 @@ module Searchkick
313
365
  end
314
366
 
315
367
  import_options = {
316
- resume: resume,
317
- async: async,
368
+ mode: (mode || :inline),
318
369
  full: true,
370
+ resume: resume,
319
371
  scope: scope
320
372
  }
321
373
 
@@ -327,7 +379,7 @@ module Searchkick
327
379
  import_before_promotion(index, relation, **import_options) if import
328
380
 
329
381
  # get existing indices to remove
330
- unless async
382
+ unless mode == :async
331
383
  check_uuid(uuid, index.uuid)
332
384
  promote(index.name, update_refresh_interval: !refresh_interval.nil?)
333
385
  clean_indices unless retain
@@ -340,8 +392,8 @@ module Searchkick
340
392
  index.import_scope(relation, **import_options) if import
341
393
  end
342
394
 
343
- if async
344
- if async.is_a?(Hash) && async[:wait]
395
+ if mode == :async
396
+ if wait
345
397
  puts "Created index: #{index.name}"
346
398
  puts "Jobs queued. Waiting..."
347
399
  loop do
@@ -367,7 +419,7 @@ module Searchkick
367
419
  end
368
420
  rescue => e
369
421
  if Searchkick.transport_error?(e) && e.message.include?("No handler for type [text]")
370
- raise UnsupportedVersionError, "This version of Searchkick requires Elasticsearch 6 or greater"
422
+ raise UnsupportedVersionError
371
423
  end
372
424
 
373
425
  raise e
@@ -379,7 +431,36 @@ module Searchkick
379
431
  # https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-index_.html#index-creation
380
432
  def check_uuid(old_uuid, new_uuid)
381
433
  if old_uuid != new_uuid
382
- raise Searchkick::Error, "Safety check failed - only run one Model.reindex per model at a time"
434
+ raise Error, "Safety check failed - only run one Model.reindex per model at a time"
435
+ end
436
+ end
437
+
438
+ def notify(record, name)
439
+ if Searchkick.callbacks_value == :bulk
440
+ yield
441
+ else
442
+ name = "#{record.class.searchkick_klass.name} #{name}" if record && record.class.searchkick_klass
443
+ event = {
444
+ name: name,
445
+ id: search_id(record)
446
+ }
447
+ ActiveSupport::Notifications.instrument("request.searchkick", event) do
448
+ yield
449
+ end
450
+ end
451
+ end
452
+
453
+ def notify_bulk(records, name)
454
+ if Searchkick.callbacks_value == :bulk
455
+ yield
456
+ else
457
+ event = {
458
+ name: "#{records.first.class.searchkick_klass.name} #{name}",
459
+ count: records.size
460
+ }
461
+ ActiveSupport::Notifications.instrument("request.searchkick", event) do
462
+ yield
463
+ end
383
464
  end
384
465
  end
385
466
  end
@@ -0,0 +1,30 @@
1
+ module Searchkick
2
+ class IndexCache
3
+ def initialize(max_size: 20)
4
+ @data = {}
5
+ @mutex = Mutex.new
6
+ @max_size = max_size
7
+ end
8
+
9
+ # probably a better pattern for this
10
+ # but keep it simple
11
+ def fetch(name)
12
+ # thread-safe in MRI without mutex
13
+ # due to how context switching works
14
+ @mutex.synchronize do
15
+ if @data.key?(name)
16
+ @data[name]
17
+ else
18
+ @data.clear if @data.size >= @max_size
19
+ @data[name] = yield
20
+ end
21
+ end
22
+ end
23
+
24
+ def clear
25
+ @mutex.synchronize do
26
+ @data.clear
27
+ end
28
+ end
29
+ end
30
+ end
@@ -7,18 +7,16 @@ module Searchkick
7
7
  end
8
8
 
9
9
  def index_options
10
- custom_mapping = options[:mappings] || {}
11
- if below70? && custom_mapping.keys.map(&:to_sym).include?(:properties)
12
- # add type
13
- custom_mapping = {index_type => custom_mapping}
14
- end
10
+ # mortal symbols are garbage collected in Ruby 2.2+
11
+ custom_settings = (options[:settings] || {}).deep_symbolize_keys
12
+ custom_mappings = (options[:mappings] || {}).deep_symbolize_keys
15
13
 
16
14
  if options[:mappings] && !options[:merge_mappings]
17
- settings = options[:settings] || {}
18
- mappings = custom_mapping
15
+ settings = custom_settings
16
+ mappings = custom_mappings
19
17
  else
20
- settings = generate_settings
21
- mappings = generate_mappings.symbolize_keys.deep_merge(custom_mapping.symbolize_keys)
18
+ settings = generate_settings.deep_symbolize_keys.deep_merge(custom_settings)
19
+ mappings = generate_mappings.deep_symbolize_keys.deep_merge(custom_mappings)
22
20
  end
23
21
 
24
22
  set_deep_paging(settings) if options[:deep_paging]
@@ -162,17 +160,14 @@ module Searchkick
162
160
  settings[:number_of_replicas] = 0
163
161
  end
164
162
 
165
- # TODO remove in Searchkick 5 (classic no longer supported)
166
163
  if options[:similarity]
167
164
  settings[:similarity] = {default: {type: options[:similarity]}}
168
165
  end
169
166
 
170
- unless below62?
171
- settings[:index] = {
172
- max_ngram_diff: 49,
173
- max_shingle_diff: 4
174
- }
175
- end
167
+ settings[:index] = {
168
+ max_ngram_diff: 49,
169
+ max_shingle_diff: 4
170
+ }
176
171
 
177
172
  if options[:case_sensitive]
178
173
  settings[:analysis][:analyzer].each do |_, analyzer|
@@ -180,13 +175,8 @@ module Searchkick
180
175
  end
181
176
  end
182
177
 
183
- # TODO do this last in Searchkick 5
184
- settings = settings.symbolize_keys.deep_merge((options[:settings] || {}).symbolize_keys)
185
-
186
178
  add_synonyms(settings)
187
179
  add_search_synonyms(settings)
188
- # TODO remove in Searchkick 5
189
- add_wordnet(settings) if options[:wordnet]
190
180
 
191
181
  if options[:special_characters] == false
192
182
  settings[:analysis][:analyzer].each_value do |analyzer_settings|
@@ -223,19 +213,7 @@ module Searchkick
223
213
  type: "smartcn"
224
214
  }
225
215
  )
226
- when "japanese"
227
- settings[:analysis][:analyzer].merge!(
228
- default_analyzer => {
229
- type: "kuromoji"
230
- },
231
- searchkick_search: {
232
- type: "kuromoji"
233
- },
234
- searchkick_search2: {
235
- type: "kuromoji"
236
- }
237
- )
238
- when "japanese2"
216
+ when "japanese", "japanese2"
239
217
  analyzer = {
240
218
  type: "custom",
241
219
  tokenizer: "kuromoji_tokenizer",
@@ -379,16 +357,15 @@ module Searchkick
379
357
  }
380
358
  end
381
359
 
382
- mapping_options = Hash[
360
+ mapping_options =
383
361
  [:suggest, :word, :text_start, :text_middle, :text_end, :word_start, :word_middle, :word_end, :highlight, :searchable, :filterable]
384
- .map { |type| [type, (options[type] || []).map(&:to_s)] }
385
- ]
362
+ .to_h { |type| [type, (options[type] || []).map(&:to_s)] }
386
363
 
387
364
  word = options[:word] != false && (!options[:match] || options[:match] == :word)
388
365
 
389
366
  mapping_options[:searchable].delete("_all")
390
367
 
391
- analyzed_field_options = {type: default_type, index: true, analyzer: default_analyzer}
368
+ analyzed_field_options = {type: default_type, index: true, analyzer: default_analyzer.to_s}
392
369
 
393
370
  mapping_options.values.flatten.uniq.each do |field|
394
371
  fields = {}
@@ -481,10 +458,6 @@ module Searchkick
481
458
  ]
482
459
  }
483
460
 
484
- if below70?
485
- mappings = {index_type => mappings}
486
- end
487
-
488
461
  mappings
489
462
  end
490
463
 
@@ -533,14 +506,14 @@ module Searchkick
533
506
  end
534
507
  settings[:analysis][:filter][:searchkick_synonym_graph] = synonym_graph
535
508
 
536
- if options[:language] == "japanese2"
509
+ if ["japanese", "japanese2"].include?(options[:language])
537
510
  [:searchkick_search, :searchkick_search2].each do |analyzer|
538
511
  settings[:analysis][:analyzer][analyzer][:filter].insert(4, "searchkick_synonym_graph")
539
512
  end
540
513
  else
541
514
  [:searchkick_search2, :searchkick_word_search].each do |analyzer|
542
515
  unless settings[:analysis][:analyzer][analyzer].key?(:filter)
543
- raise Searchkick::Error, "Search synonyms are not supported yet for language"
516
+ raise Error, "Search synonyms are not supported yet for language"
544
517
  end
545
518
 
546
519
  settings[:analysis][:analyzer][analyzer][:filter].insert(2, "searchkick_synonym_graph")
@@ -549,21 +522,6 @@ module Searchkick
549
522
  end
550
523
  end
551
524
 
552
- def add_wordnet(settings)
553
- settings[:analysis][:filter][:searchkick_wordnet] = {
554
- type: "synonym",
555
- format: "wordnet",
556
- synonyms_path: Searchkick.wordnet_path
557
- }
558
-
559
- settings[:analysis][:analyzer][default_analyzer][:filter].insert(4, "searchkick_wordnet")
560
- settings[:analysis][:analyzer][default_analyzer][:filter] << "searchkick_wordnet"
561
-
562
- %w(word_start word_middle word_end).each do |type|
563
- settings[:analysis][:analyzer]["searchkick_#{type}_index".to_sym][:filter].insert(2, "searchkick_wordnet")
564
- end
565
- end
566
-
567
525
  def set_deep_paging(settings)
568
526
  if !settings.dig(:index, :max_result_window) && !settings[:"index.max_result_window"]
569
527
  settings[:index] ||= {}
@@ -587,14 +545,6 @@ module Searchkick
587
545
  :searchkick_index
588
546
  end
589
547
 
590
- def below62?
591
- Searchkick.server_below?("6.2.0")
592
- end
593
-
594
- def below70?
595
- Searchkick.server_below?("7.0.0")
596
- end
597
-
598
548
  def below73?
599
549
  Searchkick.server_below?("7.3.0")
600
550
  end
@@ -1,3 +1,5 @@
1
+ # thread-local (technically fiber-local) indexer
2
+ # used to aggregate bulk callbacks across models
1
3
  module Searchkick
2
4
  class Indexer
3
5
  attr_reader :queued_items
@@ -14,15 +16,20 @@ module Searchkick
14
16
  def perform
15
17
  items = @queued_items
16
18
  @queued_items = []
17
- if items.any?
18
- response = Searchkick.client.bulk(body: items)
19
- if response["errors"]
20
- first_with_error = response["items"].map do |item|
21
- (item["index"] || item["delete"] || item["update"])
22
- end.find { |item| item["error"] }
23
- raise Searchkick::ImportError, "#{first_with_error["error"]} on item with id '#{first_with_error["_id"]}'"
24
- end
19
+
20
+ return if items.empty?
21
+
22
+ response = Searchkick.client.bulk(body: items)
23
+ if response["errors"]
24
+ # note: delete does not set error when item not found
25
+ first_with_error = response["items"].map do |item|
26
+ (item["index"] || item["delete"] || item["update"])
27
+ end.find { |item| item["error"] }
28
+ raise ImportError, "#{first_with_error["error"]} on item with id '#{first_with_error["_id"]}'"
25
29
  end
30
+
31
+ # maybe return response in future
32
+ nil
26
33
  end
27
34
  end
28
35
  end