searchkick 4.6.3 → 5.2.2

Sign up to get free protection for your applications and to get access to all the features.
data/lib/searchkick.rb CHANGED
@@ -1,29 +1,41 @@
1
1
  # dependencies
2
2
  require "active_support"
3
3
  require "active_support/core_ext/hash/deep_merge"
4
- require "elasticsearch"
4
+ require "active_support/core_ext/module/attr_internal"
5
+ require "active_support/core_ext/module/delegation"
6
+ require "active_support/notifications"
5
7
  require "hashie"
6
8
 
9
+ # stdlib
10
+ require "forwardable"
11
+
7
12
  # modules
8
- require "searchkick/bulk_indexer"
9
- require "searchkick/index"
10
- require "searchkick/indexer"
11
- require "searchkick/hash_wrapper"
12
- require "searchkick/middleware"
13
- require "searchkick/model"
14
- require "searchkick/multi_search"
15
- require "searchkick/query"
16
- require "searchkick/reindex_queue"
17
- require "searchkick/record_data"
18
- require "searchkick/record_indexer"
19
- require "searchkick/results"
20
- require "searchkick/version"
13
+ require_relative "searchkick/controller_runtime"
14
+ require_relative "searchkick/index"
15
+ require_relative "searchkick/index_cache"
16
+ require_relative "searchkick/index_options"
17
+ require_relative "searchkick/indexer"
18
+ require_relative "searchkick/hash_wrapper"
19
+ require_relative "searchkick/log_subscriber"
20
+ require_relative "searchkick/model"
21
+ require_relative "searchkick/multi_search"
22
+ require_relative "searchkick/query"
23
+ require_relative "searchkick/reindex_queue"
24
+ require_relative "searchkick/record_data"
25
+ require_relative "searchkick/record_indexer"
26
+ require_relative "searchkick/relation"
27
+ require_relative "searchkick/relation_indexer"
28
+ require_relative "searchkick/results"
29
+ require_relative "searchkick/version"
30
+ require_relative "searchkick/where"
21
31
 
22
32
  # integrations
23
- require "searchkick/railtie" if defined?(Rails)
24
- require "searchkick/logging" if defined?(ActiveSupport::Notifications)
33
+ require_relative "searchkick/railtie" if defined?(Rails)
25
34
 
26
35
  module Searchkick
36
+ # requires faraday
37
+ autoload :Middleware, "searchkick/middleware"
38
+
27
39
  # background jobs
28
40
  autoload :BulkReindexJob, "searchkick/bulk_reindex_job"
29
41
  autoload :ProcessBatchJob, "searchkick/process_batch_job"
@@ -33,19 +45,21 @@ module Searchkick
33
45
  # errors
34
46
  class Error < StandardError; end
35
47
  class MissingIndexError < Error; end
36
- class UnsupportedVersionError < Error; end
37
- # TODO switch to Error
38
- class InvalidQueryError < Elasticsearch::Transport::Transport::Errors::BadRequest; end
48
+ class UnsupportedVersionError < Error
49
+ def message
50
+ "This version of Searchkick requires Elasticsearch 7+ or OpenSearch 1+"
51
+ end
52
+ end
53
+ class InvalidQueryError < Error; end
39
54
  class DangerousOperation < Error; end
40
55
  class ImportError < Error; end
41
56
 
42
57
  class << self
43
- attr_accessor :search_method_name, :wordnet_path, :timeout, :models, :client_options, :redis, :index_prefix, :index_suffix, :queue_name, :model_options
58
+ attr_accessor :search_method_name, :timeout, :models, :client_options, :redis, :index_prefix, :index_suffix, :queue_name, :model_options, :client_type
44
59
  attr_writer :client, :env, :search_timeout
45
60
  attr_reader :aws_credentials
46
61
  end
47
62
  self.search_method_name = :search
48
- self.wordnet_path = "/var/lib/wn_s.pl"
49
63
  self.timeout = 10
50
64
  self.models = []
51
65
  self.client_options = {}
@@ -54,15 +68,45 @@ module Searchkick
54
68
 
55
69
  def self.client
56
70
  @client ||= begin
57
- require "typhoeus/adapters/faraday" if defined?(Typhoeus) && Gem::Version.new(Faraday::VERSION) < Gem::Version.new("0.14.0")
58
-
59
- Elasticsearch::Client.new({
60
- url: ENV["ELASTICSEARCH_URL"] || ENV["OPENSEARCH_URL"],
61
- transport_options: {request: {timeout: timeout}, headers: {content_type: "application/json"}},
62
- retry_on_failure: 2
63
- }.deep_merge(client_options)) do |f|
64
- f.use Searchkick::Middleware
65
- f.request signer_middleware_key, signer_middleware_aws_params if aws_credentials
71
+ client_type =
72
+ if self.client_type
73
+ self.client_type
74
+ elsif defined?(OpenSearch::Client) && defined?(Elasticsearch::Client)
75
+ raise Error, "Multiple clients found - set Searchkick.client_type = :elasticsearch or :opensearch"
76
+ elsif defined?(OpenSearch::Client)
77
+ :opensearch
78
+ elsif defined?(Elasticsearch::Client)
79
+ :elasticsearch
80
+ else
81
+ raise Error, "No client found - install the `elasticsearch` or `opensearch-ruby` gem"
82
+ end
83
+
84
+ # check after client to ensure faraday is installed
85
+ # TODO remove in Searchkick 6
86
+ if defined?(Typhoeus) && Gem::Version.new(Faraday::VERSION) < Gem::Version.new("0.14.0")
87
+ require "typhoeus/adapters/faraday"
88
+ end
89
+
90
+ if client_type == :opensearch
91
+ OpenSearch::Client.new({
92
+ url: ENV["OPENSEARCH_URL"],
93
+ transport_options: {request: {timeout: timeout}, headers: {content_type: "application/json"}},
94
+ retry_on_failure: 2
95
+ }.deep_merge(client_options)) do |f|
96
+ f.use Searchkick::Middleware
97
+ f.request :aws_sigv4, signer_middleware_aws_params if aws_credentials
98
+ end
99
+ else
100
+ raise Error, "The `elasticsearch` gem must be 7+" if Elasticsearch::VERSION.to_i < 7
101
+
102
+ Elasticsearch::Client.new({
103
+ url: ENV["ELASTICSEARCH_URL"],
104
+ transport_options: {request: {timeout: timeout}, headers: {content_type: "application/json"}},
105
+ retry_on_failure: 2
106
+ }.deep_merge(client_options)) do |f|
107
+ f.use Searchkick::Middleware
108
+ f.request :aws_sigv4, signer_middleware_aws_params if aws_credentials
109
+ end
66
110
  end
67
111
  end
68
112
  end
@@ -91,19 +135,12 @@ module Searchkick
91
135
  @opensearch
92
136
  end
93
137
 
94
- def self.server_below?(version)
95
- server_version = opensearch? ? "7.10.2" : self.server_version
138
+ # TODO always check true version in Searchkick 6
139
+ def self.server_below?(version, true_version = false)
140
+ server_version = !true_version && opensearch? ? "7.10.2" : self.server_version
96
141
  Gem::Version.new(server_version.split("-")[0]) < Gem::Version.new(version.split("-")[0])
97
142
  end
98
143
 
99
- # memoize for performance
100
- def self.server_below7?
101
- unless defined?(@server_below7)
102
- @server_below7 = server_below?("7.0.0")
103
- end
104
- @server_below7
105
- end
106
-
107
144
  def self.search(term = "*", model: nil, **options, &block)
108
145
  options = options.dup
109
146
  klass = model
@@ -129,17 +166,27 @@ module Searchkick
129
166
  end
130
167
  end
131
168
 
132
- options = options.merge(block: block) if block
133
- query = Searchkick::Query.new(klass, term, **options)
169
+ # TODO remove in Searchkick 6
134
170
  if options[:execute] == false
135
- query
136
- else
137
- query.execute
171
+ Searchkick.warn("The execute option is no longer needed")
172
+ options.delete(:execute)
138
173
  end
174
+
175
+ options = options.merge(block: block) if block
176
+ Relation.new(klass, term, **options)
139
177
  end
140
178
 
141
179
  def self.multi_search(queries)
142
- Searchkick::MultiSearch.new(queries).perform
180
+ return if queries.empty?
181
+
182
+ queries = queries.map { |q| q.send(:query) }
183
+ event = {
184
+ name: "Multi Search",
185
+ body: queries.flat_map { |q| [q.params.except(:body).to_json, q.body.to_json] }.map { |v| "#{v}\n" }.join,
186
+ }
187
+ ActiveSupport::Notifications.instrument("multi_search.searchkick", event) do
188
+ MultiSearch.new(queries).perform
189
+ end
143
190
  end
144
191
 
145
192
  # callbacks
@@ -160,13 +207,25 @@ module Searchkick
160
207
  end
161
208
  end
162
209
 
163
- def self.callbacks(value = nil)
210
+ # message is private
211
+ def self.callbacks(value = nil, message: nil)
164
212
  if block_given?
165
213
  previous_value = callbacks_value
166
214
  begin
167
215
  self.callbacks_value = value
168
216
  result = yield
169
- indexer.perform if callbacks_value == :bulk
217
+ if callbacks_value == :bulk && indexer.queued_items.any?
218
+ event = {}
219
+ if message
220
+ message.call(event)
221
+ else
222
+ event[:name] = "Bulk"
223
+ event[:count] = indexer.queued_items.size
224
+ end
225
+ ActiveSupport::Notifications.instrument("request.searchkick", event) do
226
+ indexer.perform
227
+ end
228
+ end
170
229
  result
171
230
  ensure
172
231
  self.callbacks_value = previous_value
@@ -177,27 +236,22 @@ module Searchkick
177
236
  end
178
237
 
179
238
  def self.aws_credentials=(creds)
180
- begin
181
- # TODO remove in Searchkick 5 (just use aws_sigv4)
182
- require "faraday_middleware/aws_signers_v4"
183
- rescue LoadError
184
- require "faraday_middleware/aws_sigv4"
185
- end
239
+ require "faraday_middleware/aws_sigv4"
240
+
186
241
  @aws_credentials = creds
187
242
  @client = nil # reset client
188
243
  end
189
244
 
190
245
  def self.reindex_status(index_name)
191
- raise Searchkick::Error, "Redis not configured" unless redis
246
+ raise Error, "Redis not configured" unless redis
192
247
 
193
- batches_left = Searchkick::Index.new(index_name).batches_left
248
+ batches_left = Index.new(index_name).batches_left
194
249
  {
195
250
  completed: batches_left == 0,
196
251
  batches_left: batches_left
197
252
  }
198
253
  end
199
254
 
200
- # TODO use ConnectionPool::Wrapper when redis is set so this is no longer needed
201
255
  def self.with_redis
202
256
  if redis
203
257
  if redis.respond_to?(:with)
@@ -215,29 +269,41 @@ module Searchkick
215
269
  end
216
270
 
217
271
  # private
218
- def self.load_records(records, ids)
219
- records =
220
- if records.respond_to?(:primary_key)
221
- # ActiveRecord
222
- records.where(records.primary_key => ids) if records.primary_key
223
- elsif records.respond_to?(:queryable)
224
- # Mongoid 3+
225
- records.queryable.for_ids(ids)
226
- elsif records.respond_to?(:unscoped) && :id.respond_to?(:in)
227
- # Nobrainer
228
- records.unscoped.where(:id.in => ids)
229
- elsif records.respond_to?(:key_column_names)
230
- records.where(records.key_column_names.first => ids)
272
+ def self.load_records(relation, ids)
273
+ relation =
274
+ if relation.respond_to?(:primary_key)
275
+ primary_key = relation.primary_key
276
+ raise Error, "Need primary key to load records" if !primary_key
277
+
278
+ relation.where(primary_key => ids)
279
+ elsif relation.respond_to?(:queryable)
280
+ relation.queryable.for_ids(ids)
231
281
  end
232
282
 
233
- raise Searchkick::Error, "Not sure how to load records" if !records
283
+ raise Error, "Not sure how to load records" if !relation
234
284
 
235
- records
285
+ relation
286
+ end
287
+
288
+ # public (for reindexing conversions)
289
+ def self.load_model(class_name, allow_child: false)
290
+ model = class_name.safe_constantize
291
+ raise Error, "Could not find class: #{class_name}" unless model
292
+ if allow_child
293
+ unless model.respond_to?(:searchkick_klass)
294
+ raise Error, "#{class_name} is not a searchkick model"
295
+ end
296
+ else
297
+ unless Searchkick.models.include?(model)
298
+ raise Error, "#{class_name} is not a searchkick model"
299
+ end
300
+ end
301
+ model
236
302
  end
237
303
 
238
304
  # private
239
305
  def self.indexer
240
- Thread.current[:searchkick_indexer] ||= Searchkick::Indexer.new
306
+ Thread.current[:searchkick_indexer] ||= Indexer.new
241
307
  end
242
308
 
243
309
  # private
@@ -250,22 +316,9 @@ module Searchkick
250
316
  Thread.current[:searchkick_callbacks_enabled] = value
251
317
  end
252
318
 
253
- # private
254
- def self.signer_middleware_key
255
- defined?(FaradayMiddleware::AwsSignersV4) ? :aws_signers_v4 : :aws_sigv4
256
- end
257
-
258
319
  # private
259
320
  def self.signer_middleware_aws_params
260
- if signer_middleware_key == :aws_sigv4
261
- {service: "es", region: "us-east-1"}.merge(aws_credentials)
262
- else
263
- {
264
- credentials: aws_credentials[:credentials] || Aws::Credentials.new(aws_credentials[:access_key_id], aws_credentials[:secret_access_key]),
265
- service_name: "es",
266
- region: aws_credentials[:region] || "us-east-1"
267
- }
268
- end
321
+ {service: "es", region: "us-east-1"}.merge(aws_credentials)
269
322
  end
270
323
 
271
324
  # private
@@ -275,31 +328,55 @@ module Searchkick
275
328
  def self.relation?(klass)
276
329
  if klass.respond_to?(:current_scope)
277
330
  !klass.current_scope.nil?
278
- elsif defined?(Mongoid::Threaded)
279
- !Mongoid::Threaded.current_scope(klass).nil?
331
+ else
332
+ klass.is_a?(Mongoid::Criteria) || !Mongoid::Threaded.current_scope(klass).nil?
333
+ end
334
+ end
335
+
336
+ # private
337
+ def self.scope(model)
338
+ # safety check to make sure used properly in code
339
+ raise Error, "Cannot scope relation" if relation?(model)
340
+
341
+ if model.searchkick_options[:unscope]
342
+ model.unscoped
343
+ else
344
+ model
280
345
  end
281
346
  end
282
347
 
283
348
  # private
284
349
  def self.not_found_error?(e)
285
- (defined?(Elasticsearch) && e.is_a?(Elasticsearch::Transport::Transport::Errors::NotFound)) ||
350
+ (defined?(Elastic::Transport) && e.is_a?(Elastic::Transport::Transport::Errors::NotFound)) ||
351
+ (defined?(Elasticsearch::Transport) && e.is_a?(Elasticsearch::Transport::Transport::Errors::NotFound)) ||
286
352
  (defined?(OpenSearch) && e.is_a?(OpenSearch::Transport::Transport::Errors::NotFound))
287
353
  end
288
354
 
289
355
  # private
290
356
  def self.transport_error?(e)
291
- (defined?(Elasticsearch) && e.is_a?(Elasticsearch::Transport::Transport::Error)) ||
357
+ (defined?(Elastic::Transport) && e.is_a?(Elastic::Transport::Transport::Error)) ||
358
+ (defined?(Elasticsearch::Transport) && e.is_a?(Elasticsearch::Transport::Transport::Error)) ||
292
359
  (defined?(OpenSearch) && e.is_a?(OpenSearch::Transport::Transport::Error))
293
360
  end
294
- end
295
361
 
296
- require "active_model/callbacks"
297
- ActiveModel::Callbacks.include(Searchkick::Model)
298
- # TODO use
299
- # ActiveSupport.on_load(:mongoid) do
300
- # Mongoid::Document::ClassMethods.include Searchkick::Model
301
- # end
362
+ # private
363
+ def self.not_allowed_error?(e)
364
+ (defined?(Elastic::Transport) && e.is_a?(Elastic::Transport::Transport::Errors::MethodNotAllowed)) ||
365
+ (defined?(Elasticsearch::Transport) && e.is_a?(Elasticsearch::Transport::Transport::Errors::MethodNotAllowed)) ||
366
+ (defined?(OpenSearch) && e.is_a?(OpenSearch::Transport::Transport::Errors::MethodNotAllowed))
367
+ end
368
+ end
302
369
 
303
370
  ActiveSupport.on_load(:active_record) do
304
371
  extend Searchkick::Model
305
372
  end
373
+
374
+ ActiveSupport.on_load(:mongoid) do
375
+ Mongoid::Document::ClassMethods.include Searchkick::Model
376
+ end
377
+
378
+ ActiveSupport.on_load(:action_controller) do
379
+ include Searchkick::ControllerRuntime
380
+ end
381
+
382
+ Searchkick::LogSubscriber.attach_to :searchkick
@@ -4,9 +4,12 @@ namespace :searchkick do
4
4
  class_name = ENV["CLASS"]
5
5
  abort "USAGE: rake searchkick:reindex CLASS=Product" unless class_name
6
6
 
7
- model = class_name.safe_constantize
8
- abort "Could not find class: #{class_name}" unless model
9
- abort "#{class_name} is not a searchkick model" unless Searchkick.models.include?(model)
7
+ model =
8
+ begin
9
+ Searchkick.load_model(class_name)
10
+ rescue Searchkick::Error => e
11
+ abort e.message
12
+ end
10
13
 
11
14
  puts "Reindexing #{model.name}..."
12
15
  model.reindex
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: searchkick
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.6.3
4
+ version: 5.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Kane
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-11-19 00:00:00.000000000 Z
11
+ date: 2023-04-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemodel
@@ -16,34 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '5'
19
+ version: '5.2'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: '5'
27
- - !ruby/object:Gem::Dependency
28
- name: elasticsearch
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - ">="
32
- - !ruby/object:Gem::Version
33
- version: '6'
34
- - - "<"
35
- - !ruby/object:Gem::Version
36
- version: '7.14'
37
- type: :runtime
38
- prerelease: false
39
- version_requirements: !ruby/object:Gem::Requirement
40
- requirements:
41
- - - ">="
42
- - !ruby/object:Gem::Version
43
- version: '6'
44
- - - "<"
45
- - !ruby/object:Gem::Version
46
- version: '7.14'
26
+ version: '5.2'
47
27
  - !ruby/object:Gem::Dependency
48
28
  name: hashie
49
29
  requirement: !ruby/object:Gem::Requirement
@@ -68,13 +48,14 @@ files:
68
48
  - LICENSE.txt
69
49
  - README.md
70
50
  - lib/searchkick.rb
71
- - lib/searchkick/bulk_indexer.rb
72
51
  - lib/searchkick/bulk_reindex_job.rb
52
+ - lib/searchkick/controller_runtime.rb
73
53
  - lib/searchkick/hash_wrapper.rb
74
54
  - lib/searchkick/index.rb
55
+ - lib/searchkick/index_cache.rb
75
56
  - lib/searchkick/index_options.rb
76
57
  - lib/searchkick/indexer.rb
77
- - lib/searchkick/logging.rb
58
+ - lib/searchkick/log_subscriber.rb
78
59
  - lib/searchkick/middleware.rb
79
60
  - lib/searchkick/model.rb
80
61
  - lib/searchkick/multi_search.rb
@@ -86,8 +67,11 @@ files:
86
67
  - lib/searchkick/record_indexer.rb
87
68
  - lib/searchkick/reindex_queue.rb
88
69
  - lib/searchkick/reindex_v2_job.rb
70
+ - lib/searchkick/relation.rb
71
+ - lib/searchkick/relation_indexer.rb
89
72
  - lib/searchkick/results.rb
90
73
  - lib/searchkick/version.rb
74
+ - lib/searchkick/where.rb
91
75
  - lib/tasks/searchkick.rake
92
76
  homepage: https://github.com/ankane/searchkick
93
77
  licenses:
@@ -101,14 +85,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
101
85
  requirements:
102
86
  - - ">="
103
87
  - !ruby/object:Gem::Version
104
- version: '2.4'
88
+ version: '2.6'
105
89
  required_rubygems_version: !ruby/object:Gem::Requirement
106
90
  requirements:
107
91
  - - ">="
108
92
  - !ruby/object:Gem::Version
109
93
  version: '0'
110
94
  requirements: []
111
- rubygems_version: 3.2.22
95
+ rubygems_version: 3.4.10
112
96
  signing_key:
113
97
  specification_version: 4
114
98
  summary: Intelligent search made easy with Rails and Elasticsearch or OpenSearch
@@ -1,173 +0,0 @@
1
- module Searchkick
2
- class BulkIndexer
3
- attr_reader :index
4
-
5
- def initialize(index)
6
- @index = index
7
- end
8
-
9
- def import_scope(relation, resume: false, method_name: nil, async: false, batch: false, batch_id: nil, full: false, scope: nil)
10
- if scope
11
- relation = relation.send(scope)
12
- elsif relation.respond_to?(:search_import)
13
- relation = relation.search_import
14
- end
15
-
16
- if batch
17
- import_or_update relation.to_a, method_name, async
18
- Searchkick.with_redis { |r| r.srem(batches_key, batch_id) } if batch_id
19
- elsif full && async
20
- full_reindex_async(relation)
21
- elsif relation.respond_to?(:find_in_batches)
22
- if resume
23
- # use total docs instead of max id since there's not a great way
24
- # to get the max _id without scripting since it's a string
25
-
26
- # TODO use primary key and prefix with table name
27
- relation = relation.where("id > ?", index.total_docs)
28
- end
29
-
30
- relation = relation.select("id").except(:includes, :preload) if async
31
-
32
- relation.find_in_batches batch_size: batch_size do |items|
33
- import_or_update items, method_name, async
34
- end
35
- else
36
- each_batch(relation) do |items|
37
- import_or_update items, method_name, async
38
- end
39
- end
40
- end
41
-
42
- def bulk_index(records)
43
- Searchkick.indexer.queue(records.map { |r| RecordData.new(index, r).index_data })
44
- end
45
-
46
- def bulk_delete(records)
47
- Searchkick.indexer.queue(records.reject { |r| r.id.blank? }.map { |r| RecordData.new(index, r).delete_data })
48
- end
49
-
50
- def bulk_update(records, method_name)
51
- Searchkick.indexer.queue(records.map { |r| RecordData.new(index, r).update_data(method_name) })
52
- end
53
-
54
- def batches_left
55
- Searchkick.with_redis { |r| r.scard(batches_key) }
56
- end
57
-
58
- private
59
-
60
- def import_or_update(records, method_name, async)
61
- if records.any?
62
- if async
63
- Searchkick::BulkReindexJob.perform_later(
64
- class_name: records.first.class.searchkick_options[:class_name],
65
- record_ids: records.map(&:id),
66
- index_name: index.name,
67
- method_name: method_name ? method_name.to_s : nil
68
- )
69
- else
70
- records = records.select(&:should_index?)
71
- if records.any?
72
- with_retries do
73
- # call out to index for ActiveSupport notifications
74
- if method_name
75
- index.bulk_update(records, method_name)
76
- else
77
- index.bulk_index(records)
78
- end
79
- end
80
- end
81
- end
82
- end
83
- end
84
-
85
- def full_reindex_async(scope)
86
- if scope.respond_to?(:primary_key)
87
- # TODO expire Redis key
88
- primary_key = scope.primary_key
89
-
90
- scope = scope.select(primary_key).except(:includes, :preload)
91
-
92
- starting_id =
93
- begin
94
- scope.minimum(primary_key)
95
- rescue ActiveRecord::StatementInvalid
96
- false
97
- end
98
-
99
- if starting_id.nil?
100
- # no records, do nothing
101
- elsif starting_id.is_a?(Numeric)
102
- max_id = scope.maximum(primary_key)
103
- batches_count = ((max_id - starting_id + 1) / batch_size.to_f).ceil
104
-
105
- batches_count.times do |i|
106
- batch_id = i + 1
107
- min_id = starting_id + (i * batch_size)
108
- bulk_reindex_job scope, batch_id, min_id: min_id, max_id: min_id + batch_size - 1
109
- end
110
- else
111
- scope.find_in_batches(batch_size: batch_size).each_with_index do |batch, i|
112
- batch_id = i + 1
113
-
114
- bulk_reindex_job scope, batch_id, record_ids: batch.map { |record| record.id.to_s }
115
- end
116
- end
117
- else
118
- batch_id = 1
119
- # TODO remove any eager loading
120
- scope = scope.only(:_id) if scope.respond_to?(:only)
121
- each_batch(scope) do |items|
122
- bulk_reindex_job scope, batch_id, record_ids: items.map { |i| i.id.to_s }
123
- batch_id += 1
124
- end
125
- end
126
- end
127
-
128
- def each_batch(scope)
129
- # https://github.com/karmi/tire/blob/master/lib/tire/model/import.rb
130
- # use cursor for Mongoid
131
- items = []
132
- scope.all.each do |item|
133
- items << item
134
- if items.length == batch_size
135
- yield items
136
- items = []
137
- end
138
- end
139
- yield items if items.any?
140
- end
141
-
142
- def bulk_reindex_job(scope, batch_id, options)
143
- Searchkick.with_redis { |r| r.sadd(batches_key, batch_id) }
144
- Searchkick::BulkReindexJob.perform_later(**{
145
- class_name: scope.searchkick_options[:class_name],
146
- index_name: index.name,
147
- batch_id: batch_id
148
- }.merge(options))
149
- end
150
-
151
- def with_retries
152
- retries = 0
153
-
154
- begin
155
- yield
156
- rescue Faraday::ClientError => e
157
- if retries < 1
158
- retries += 1
159
- retry
160
- end
161
- raise e
162
- end
163
- end
164
-
165
- def batches_key
166
- "searchkick:reindex:#{index.name}:batches"
167
- end
168
-
169
- def batch_size
170
- @batch_size ||= index.options[:batch_size] || 1000
171
- end
172
- end
173
- end