chewy 7.2.1 → 7.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/CODEOWNERS +1 -0
- data/.github/dependabot.yml +42 -0
- data/.github/workflows/ruby.yml +28 -26
- data/.rubocop.yml +4 -1
- data/CHANGELOG.md +196 -0
- data/Gemfile +4 -3
- data/README.md +203 -20
- data/chewy.gemspec +4 -18
- data/gemfiles/base.gemfile +12 -0
- data/gemfiles/rails.6.1.activerecord.gemfile +2 -1
- data/gemfiles/{rails.5.2.activerecord.gemfile → rails.7.0.activerecord.gemfile} +6 -3
- data/gemfiles/{rails.6.0.activerecord.gemfile → rails.7.1.activerecord.gemfile} +6 -3
- data/lib/chewy/config.rb +22 -14
- data/lib/chewy/elastic_client.rb +31 -0
- data/lib/chewy/errors.rb +11 -2
- data/lib/chewy/fields/base.rb +69 -13
- data/lib/chewy/fields/root.rb +2 -10
- data/lib/chewy/index/actions.rb +11 -16
- data/lib/chewy/index/adapter/active_record.rb +18 -3
- data/lib/chewy/index/adapter/object.rb +0 -10
- data/lib/chewy/index/adapter/orm.rb +4 -14
- data/lib/chewy/index/crutch.rb +15 -7
- data/lib/chewy/index/import/bulk_builder.rb +219 -32
- data/lib/chewy/index/import/bulk_request.rb +1 -1
- data/lib/chewy/index/import/routine.rb +3 -3
- data/lib/chewy/index/import.rb +45 -31
- data/lib/chewy/index/mapping.rb +2 -2
- data/lib/chewy/index/observe/active_record_methods.rb +87 -0
- data/lib/chewy/index/observe/callback.rb +34 -0
- data/lib/chewy/index/observe.rb +3 -58
- data/lib/chewy/index/syncer.rb +1 -1
- data/lib/chewy/index.rb +25 -0
- data/lib/chewy/journal.rb +17 -6
- data/lib/chewy/log_subscriber.rb +5 -1
- data/lib/chewy/minitest/helpers.rb +77 -0
- data/lib/chewy/minitest/search_index_receiver.rb +3 -1
- data/lib/chewy/rake_helper.rb +92 -11
- data/lib/chewy/rspec/build_query.rb +12 -0
- data/lib/chewy/rspec/helpers.rb +55 -0
- data/lib/chewy/rspec/update_index.rb +14 -7
- data/lib/chewy/rspec.rb +2 -0
- data/lib/chewy/runtime/version.rb +1 -1
- data/lib/chewy/runtime.rb +1 -1
- data/lib/chewy/search/parameters/collapse.rb +16 -0
- data/lib/chewy/search/parameters/ignore_unavailable.rb +27 -0
- data/lib/chewy/search/parameters/indices.rb +1 -1
- data/lib/chewy/search/parameters/knn.rb +16 -0
- data/lib/chewy/search/parameters/order.rb +6 -19
- data/lib/chewy/search/parameters/storage.rb +1 -1
- data/lib/chewy/search/parameters/track_total_hits.rb +16 -0
- data/lib/chewy/search/parameters.rb +4 -4
- data/lib/chewy/search/request.rb +74 -16
- data/lib/chewy/search/scoping.rb +1 -1
- data/lib/chewy/search.rb +5 -2
- data/lib/chewy/stash.rb +3 -3
- data/lib/chewy/strategy/active_job.rb +1 -1
- data/lib/chewy/strategy/atomic_no_refresh.rb +18 -0
- data/lib/chewy/strategy/base.rb +10 -0
- data/lib/chewy/strategy/delayed_sidekiq/scheduler.rb +168 -0
- data/lib/chewy/strategy/delayed_sidekiq/worker.rb +76 -0
- data/lib/chewy/strategy/delayed_sidekiq.rb +30 -0
- data/lib/chewy/strategy/lazy_sidekiq.rb +64 -0
- data/lib/chewy/strategy/sidekiq.rb +1 -1
- data/lib/chewy/strategy.rb +3 -0
- data/lib/chewy/version.rb +1 -1
- data/lib/chewy.rb +21 -14
- data/lib/tasks/chewy.rake +18 -2
- data/migration_guide.md +1 -1
- data/spec/chewy/config_spec.rb +2 -2
- data/spec/chewy/elastic_client_spec.rb +26 -0
- data/spec/chewy/fields/base_spec.rb +39 -18
- data/spec/chewy/index/actions_spec.rb +10 -10
- data/spec/chewy/index/adapter/active_record_spec.rb +88 -0
- data/spec/chewy/index/import/bulk_builder_spec.rb +309 -1
- data/spec/chewy/index/import/routine_spec.rb +5 -5
- data/spec/chewy/index/import_spec.rb +48 -26
- data/spec/chewy/index/observe/active_record_methods_spec.rb +68 -0
- data/spec/chewy/index/observe/callback_spec.rb +139 -0
- data/spec/chewy/index/observe_spec.rb +27 -0
- data/spec/chewy/journal_spec.rb +13 -49
- data/spec/chewy/minitest/helpers_spec.rb +111 -1
- data/spec/chewy/minitest/search_index_receiver_spec.rb +6 -4
- data/spec/chewy/rake_helper_spec.rb +170 -0
- data/spec/chewy/rspec/build_query_spec.rb +34 -0
- data/spec/chewy/rspec/helpers_spec.rb +61 -0
- data/spec/chewy/search/pagination/kaminari_examples.rb +1 -1
- data/spec/chewy/search/pagination/kaminari_spec.rb +1 -1
- data/spec/chewy/search/parameters/collapse_spec.rb +5 -0
- data/spec/chewy/search/parameters/ignore_unavailable_spec.rb +67 -0
- data/spec/chewy/search/parameters/knn_spec.rb +5 -0
- data/spec/chewy/search/parameters/order_spec.rb +18 -11
- data/spec/chewy/search/parameters/track_total_hits_spec.rb +5 -0
- data/spec/chewy/search/parameters_spec.rb +6 -1
- data/spec/chewy/search/request_spec.rb +58 -9
- data/spec/chewy/search_spec.rb +9 -0
- data/spec/chewy/strategy/active_job_spec.rb +8 -8
- data/spec/chewy/strategy/atomic_no_refresh_spec.rb +60 -0
- data/spec/chewy/strategy/delayed_sidekiq_spec.rb +208 -0
- data/spec/chewy/strategy/lazy_sidekiq_spec.rb +214 -0
- data/spec/chewy/strategy/sidekiq_spec.rb +4 -4
- data/spec/chewy_spec.rb +10 -7
- data/spec/spec_helper.rb +1 -2
- data/spec/support/active_record.rb +8 -1
- metadata +45 -264
- data/lib/chewy/backports/deep_dup.rb +0 -46
- data/lib/chewy/backports/duplicable.rb +0 -91
- data/lib/chewy/index/import/thread_safe_progress_bar.rb +0 -40
data/lib/chewy/index/import.rb
CHANGED
@@ -2,19 +2,17 @@ require 'chewy/index/import/journal_builder'
|
|
2
2
|
require 'chewy/index/import/bulk_builder'
|
3
3
|
require 'chewy/index/import/bulk_request'
|
4
4
|
require 'chewy/index/import/routine'
|
5
|
-
require 'chewy/index/import/thread_safe_progress_bar'
|
6
5
|
|
7
6
|
module Chewy
|
8
7
|
class Index
|
9
8
|
module Import
|
10
9
|
extend ActiveSupport::Concern
|
11
10
|
|
12
|
-
IMPORT_WORKER = lambda do |index, options, total,
|
11
|
+
IMPORT_WORKER = lambda do |index, options, total, ids, iteration|
|
13
12
|
::Process.setproctitle("chewy [#{index}]: import data (#{iteration + 1}/#{total})")
|
14
13
|
routine = Routine.new(index, **options)
|
15
14
|
index.adapter.import(*ids, routine.options) do |action_objects|
|
16
15
|
routine.process(**action_objects)
|
17
|
-
progress_bar.increment(action_objects.map { |_, v| v.size }.sum) if routine.options[:progressbar]
|
18
16
|
end
|
19
17
|
{errors: routine.errors, import: routine.stats, leftovers: routine.leftovers}
|
20
18
|
end
|
@@ -38,8 +36,7 @@ module Chewy
|
|
38
36
|
# passed objects from the index if they are not in the default scope
|
39
37
|
# or marked for destruction.
|
40
38
|
#
|
41
|
-
# It handles parent-child relationships
|
42
|
-
# changed it destroys the object and recreates it from scratch.
|
39
|
+
# It handles parent-child relationships with a join field reindexing children when the parent is reindexed.
|
43
40
|
#
|
44
41
|
# Performs journaling if enabled: it stores all the ids of the imported
|
45
42
|
# objects to a specialized index. It is possible to replay particular import
|
@@ -76,7 +73,7 @@ module Chewy
|
|
76
73
|
# @option options [true, Integer, Hash] parallel enables parallel import processing with the Parallel gem, accepts the number of workers or any Parallel gem acceptable options
|
77
74
|
# @return [true, false] false in case of errors
|
78
75
|
def import(*args)
|
79
|
-
|
76
|
+
intercept_import_using_strategy(*args).blank?
|
80
77
|
end
|
81
78
|
|
82
79
|
# @!method import!(*collection, **options)
|
@@ -87,7 +84,8 @@ module Chewy
|
|
87
84
|
#
|
88
85
|
# @raise [Chewy::ImportFailed] in case of errors
|
89
86
|
def import!(*args)
|
90
|
-
errors =
|
87
|
+
errors = intercept_import_using_strategy(*args)
|
88
|
+
|
91
89
|
raise Chewy::ImportFailed.new(self, errors) if errors.present?
|
92
90
|
|
93
91
|
true
|
@@ -129,6 +127,32 @@ module Chewy
|
|
129
127
|
|
130
128
|
private
|
131
129
|
|
130
|
+
def intercept_import_using_strategy(*args)
|
131
|
+
args_clone = args.deep_dup
|
132
|
+
options = args_clone.extract_options!
|
133
|
+
strategy = options.delete(:strategy)
|
134
|
+
|
135
|
+
return import_routine(*args) if strategy.blank?
|
136
|
+
|
137
|
+
ids = args_clone.flatten
|
138
|
+
return {} if ids.blank?
|
139
|
+
return {argument: {"#{strategy} supports ids only!" => ids}} unless ids.all? do |id|
|
140
|
+
id.respond_to?(:to_i)
|
141
|
+
end
|
142
|
+
|
143
|
+
case strategy
|
144
|
+
when :delayed_sidekiq
|
145
|
+
begin
|
146
|
+
Chewy::Strategy::DelayedSidekiq::Scheduler.new(self, ids, options).postpone
|
147
|
+
{} # success. errors handling convention
|
148
|
+
rescue StandardError => e
|
149
|
+
{scheduler: {e.message => ids}}
|
150
|
+
end
|
151
|
+
else
|
152
|
+
{argument: {"unsupported strategy: '#{strategy}'" => ids}}
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
132
156
|
def import_routine(*args)
|
133
157
|
return if !args.first.nil? && empty_objects_or_scope?(args.first)
|
134
158
|
|
@@ -152,10 +176,8 @@ module Chewy
|
|
152
176
|
|
153
177
|
def import_linear(objects, routine)
|
154
178
|
ActiveSupport::Notifications.instrument 'import_objects.chewy', index: self do |payload|
|
155
|
-
progress_bar = ThreadSafeProgressBar.new(routine.options[:progressbar]) { adapter.import_count(objects) }
|
156
179
|
adapter.import(*objects, routine.options) do |action_objects|
|
157
180
|
routine.process(**action_objects)
|
158
|
-
progress_bar.increment(action_objects.map { |_, v| v.size }.sum) if routine.options[:progressbar]
|
159
181
|
end
|
160
182
|
routine.perform_bulk(routine.leftovers)
|
161
183
|
payload[:import] = routine.stats
|
@@ -170,20 +192,24 @@ module Chewy
|
|
170
192
|
ActiveSupport::Notifications.instrument 'import_objects.chewy', index: self do |payload|
|
171
193
|
batches = adapter.import_references(*objects, routine.options.slice(:batch_size)).to_a
|
172
194
|
|
173
|
-
progress_bar = ThreadSafeProgressBar.new(routine.options[:progressbar]) { adapter.import_count(objects) }
|
174
195
|
::ActiveRecord::Base.connection.close if defined?(::ActiveRecord::Base)
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
end
|
181
|
-
end
|
182
|
-
|
196
|
+
results = ::Parallel.map_with_index(
|
197
|
+
batches,
|
198
|
+
routine.parallel_options,
|
199
|
+
&IMPORT_WORKER.curry[self, routine.options, batches.size]
|
200
|
+
)
|
183
201
|
::ActiveRecord::Base.connection.reconnect! if defined?(::ActiveRecord::Base)
|
184
202
|
errors, import, leftovers = process_parallel_import_results(results)
|
185
203
|
|
186
|
-
|
204
|
+
if leftovers.present?
|
205
|
+
batches = leftovers.each_slice(routine.options[:batch_size])
|
206
|
+
results = ::Parallel.map_with_index(
|
207
|
+
batches,
|
208
|
+
routine.parallel_options,
|
209
|
+
&LEFTOVERS_WORKER.curry[self, routine.options, batches.size]
|
210
|
+
)
|
211
|
+
errors.concat(results.flatten(1))
|
212
|
+
end
|
187
213
|
|
188
214
|
payload[:import] = import
|
189
215
|
payload[:errors] = payload_errors(errors) if errors.present?
|
@@ -191,18 +217,6 @@ module Chewy
|
|
191
217
|
end
|
192
218
|
end
|
193
219
|
|
194
|
-
def execute_leftovers(leftovers, routine, self_object, errors)
|
195
|
-
return unless leftovers.present?
|
196
|
-
|
197
|
-
batches = leftovers.each_slice(routine.options[:batch_size])
|
198
|
-
results = ::Parallel.map_with_index(
|
199
|
-
batches,
|
200
|
-
routine.parallel_options,
|
201
|
-
&LEFTOVERS_WORKER.curry[self_object, routine.options, batches.size]
|
202
|
-
)
|
203
|
-
errors.concat(results.flatten(1))
|
204
|
-
end
|
205
|
-
|
206
220
|
def process_parallel_import_results(results)
|
207
221
|
results.each_with_object([[], {}, []]) do |r, (e, i, l)|
|
208
222
|
e.concat(r[:errors])
|
data/lib/chewy/index/mapping.rb
CHANGED
@@ -170,8 +170,8 @@ module Chewy
|
|
170
170
|
# template /tit.+/, type: 'text', mapping_hash # "match_mapping_type" as an optional second argument
|
171
171
|
# template template42: {match: 'hello*', mapping: {type: 'object'}} # or even pass a template as is
|
172
172
|
#
|
173
|
-
def template(*args)
|
174
|
-
root.dynamic_template(*args)
|
173
|
+
def template(*args, **options)
|
174
|
+
root.dynamic_template(*args, **options)
|
175
175
|
end
|
176
176
|
alias_method :dynamic_template, :template
|
177
177
|
|
@@ -0,0 +1,87 @@
|
|
1
|
+
module Chewy
|
2
|
+
class Index
|
3
|
+
module Observe
|
4
|
+
module Helpers
|
5
|
+
def update_proc(index_name, *args, &block)
|
6
|
+
options = args.extract_options!
|
7
|
+
method = args.first
|
8
|
+
|
9
|
+
proc do
|
10
|
+
reference = if index_name.is_a?(Proc)
|
11
|
+
if index_name.arity.zero?
|
12
|
+
instance_exec(&index_name)
|
13
|
+
else
|
14
|
+
index_name.call(self)
|
15
|
+
end
|
16
|
+
else
|
17
|
+
index_name
|
18
|
+
end
|
19
|
+
|
20
|
+
index = Chewy.derive_name(reference)
|
21
|
+
|
22
|
+
next if Chewy.strategy.current.name == :bypass
|
23
|
+
|
24
|
+
backreference = if method && method.to_s == 'self'
|
25
|
+
self
|
26
|
+
elsif method
|
27
|
+
send(method)
|
28
|
+
else
|
29
|
+
instance_eval(&block)
|
30
|
+
end
|
31
|
+
|
32
|
+
index.update_index(backreference, options)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def extract_callback_options!(args)
|
37
|
+
options = args.extract_options!
|
38
|
+
result = options.each_key.with_object({}) do |key, hash|
|
39
|
+
hash[key] = options.delete(key) if %i[if unless].include?(key)
|
40
|
+
end
|
41
|
+
args.push(options) unless options.empty?
|
42
|
+
result
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
extend Helpers
|
47
|
+
|
48
|
+
module ActiveRecordMethods
|
49
|
+
extend ActiveSupport::Concern
|
50
|
+
|
51
|
+
def run_chewy_callbacks
|
52
|
+
chewy_callbacks.each { |callback| callback.call(self) }
|
53
|
+
end
|
54
|
+
|
55
|
+
def update_chewy_indices
|
56
|
+
Chewy.strategy.current.update_chewy_indices(self)
|
57
|
+
end
|
58
|
+
|
59
|
+
included do
|
60
|
+
class_attribute :chewy_callbacks, default: []
|
61
|
+
end
|
62
|
+
|
63
|
+
class_methods do
|
64
|
+
def initialize_chewy_callbacks
|
65
|
+
if Chewy.use_after_commit_callbacks
|
66
|
+
after_commit :update_chewy_indices, on: %i[create update]
|
67
|
+
after_commit :run_chewy_callbacks, on: :destroy
|
68
|
+
else
|
69
|
+
after_save :update_chewy_indices
|
70
|
+
after_destroy :run_chewy_callbacks
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def update_index(type_name, *args, &block)
|
75
|
+
callback_options = Observe.extract_callback_options!(args)
|
76
|
+
update_proc = Observe.update_proc(type_name, *args, &block)
|
77
|
+
callback = Chewy::Index::Observe::Callback.new(update_proc, callback_options)
|
78
|
+
|
79
|
+
initialize_chewy_callbacks if chewy_callbacks.empty?
|
80
|
+
|
81
|
+
self.chewy_callbacks = chewy_callbacks.dup << callback
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Chewy
|
2
|
+
class Index
|
3
|
+
module Observe
|
4
|
+
class Callback
|
5
|
+
def initialize(executable, filters = {})
|
6
|
+
@executable = executable
|
7
|
+
@if_filter = filters[:if]
|
8
|
+
@unless_filter = filters[:unless]
|
9
|
+
end
|
10
|
+
|
11
|
+
def call(context)
|
12
|
+
return if !@if_filter.nil? && !eval_filter(@if_filter, context)
|
13
|
+
return if !@unless_filter.nil? && eval_filter(@unless_filter, context)
|
14
|
+
|
15
|
+
eval_proc(@executable, context)
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def eval_filter(filter, context)
|
21
|
+
case filter
|
22
|
+
when Symbol then context.send(filter)
|
23
|
+
when Proc then eval_proc(filter, context)
|
24
|
+
else filter
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def eval_proc(executable, context)
|
29
|
+
executable.arity.zero? ? context.instance_exec(&executable) : executable.call(context)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/chewy/index/observe.rb
CHANGED
@@ -1,66 +1,11 @@
|
|
1
|
+
require 'chewy/index/observe/callback'
|
2
|
+
require 'chewy/index/observe/active_record_methods'
|
3
|
+
|
1
4
|
module Chewy
|
2
5
|
class Index
|
3
6
|
module Observe
|
4
7
|
extend ActiveSupport::Concern
|
5
8
|
|
6
|
-
module Helpers
|
7
|
-
def update_proc(index_name, *args, &block)
|
8
|
-
options = args.extract_options!
|
9
|
-
method = args.first
|
10
|
-
|
11
|
-
proc do
|
12
|
-
reference = if index_name.is_a?(Proc)
|
13
|
-
if index_name.arity.zero?
|
14
|
-
instance_exec(&index_name)
|
15
|
-
else
|
16
|
-
index_name.call(self)
|
17
|
-
end
|
18
|
-
else
|
19
|
-
index_name
|
20
|
-
end
|
21
|
-
|
22
|
-
index = Chewy.derive_name(reference)
|
23
|
-
|
24
|
-
next if Chewy.strategy.current.name == :bypass
|
25
|
-
|
26
|
-
backreference = if method && method.to_s == 'self'
|
27
|
-
self
|
28
|
-
elsif method
|
29
|
-
send(method)
|
30
|
-
else
|
31
|
-
instance_eval(&block)
|
32
|
-
end
|
33
|
-
|
34
|
-
index.update_index(backreference, options)
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
def extract_callback_options!(args)
|
39
|
-
options = args.extract_options!
|
40
|
-
result = options.each_key.with_object({}) do |key, hash|
|
41
|
-
hash[key] = options.delete(key) if %i[if unless].include?(key)
|
42
|
-
end
|
43
|
-
args.push(options) unless options.empty?
|
44
|
-
result
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
extend Helpers
|
49
|
-
|
50
|
-
module ActiveRecordMethods
|
51
|
-
def update_index(type_name, *args, &block)
|
52
|
-
callback_options = Observe.extract_callback_options!(args)
|
53
|
-
update_proc = Observe.update_proc(type_name, *args, &block)
|
54
|
-
|
55
|
-
if Chewy.use_after_commit_callbacks
|
56
|
-
after_commit(**callback_options, &update_proc)
|
57
|
-
else
|
58
|
-
after_save(**callback_options, &update_proc)
|
59
|
-
after_destroy(**callback_options, &update_proc)
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
9
|
module ClassMethods
|
65
10
|
def update_index(objects, options = {})
|
66
11
|
Chewy.strategy.current.update(self, objects, options)
|
data/lib/chewy/index/syncer.rb
CHANGED
@@ -27,7 +27,7 @@ module Chewy
|
|
27
27
|
# @see Chewy::Index::Actions::ClassMethods#sync
|
28
28
|
class Syncer
|
29
29
|
DEFAULT_SYNC_BATCH_SIZE = 20_000
|
30
|
-
ISO_DATETIME = /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(\.\d+)?\z
|
30
|
+
ISO_DATETIME = /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(\.\d+)?\z/
|
31
31
|
OUTDATED_IDS_WORKER = lambda do |outdated_sync_field_type, source_data_hash, index, total, index_data|
|
32
32
|
::Process.setproctitle("chewy [#{index}]: sync outdated calculation (#{::Parallel.worker_number + 1}/#{total})") if index
|
33
33
|
index_data.each_with_object([]) do |(id, index_sync_value), result|
|
data/lib/chewy/index.rb
CHANGED
@@ -20,6 +20,10 @@ module Chewy
|
|
20
20
|
pipeline raw_import refresh replication
|
21
21
|
].freeze
|
22
22
|
|
23
|
+
STRATEGY_OPTIONS = {
|
24
|
+
delayed_sidekiq: %i[latency margin ttl reindex_wrapper]
|
25
|
+
}.freeze
|
26
|
+
|
23
27
|
include Search
|
24
28
|
include Actions
|
25
29
|
include Aliases
|
@@ -221,6 +225,27 @@ module Chewy
|
|
221
225
|
params.assert_valid_keys(IMPORT_OPTIONS_KEYS)
|
222
226
|
self._default_import_options = _default_import_options.merge(params)
|
223
227
|
end
|
228
|
+
|
229
|
+
def strategy_config(params = {})
|
230
|
+
@strategy_config ||= begin
|
231
|
+
config_struct = Struct.new(*STRATEGY_OPTIONS.keys).new
|
232
|
+
|
233
|
+
STRATEGY_OPTIONS.each_with_object(config_struct) do |(strategy, options), res|
|
234
|
+
res[strategy] = case strategy
|
235
|
+
when :delayed_sidekiq
|
236
|
+
Struct.new(*STRATEGY_OPTIONS[strategy]).new.tap do |config|
|
237
|
+
options.each do |option|
|
238
|
+
config[option] = params.dig(strategy, option) || Chewy.configuration.dig(:strategy_config, strategy, option)
|
239
|
+
end
|
240
|
+
|
241
|
+
config[:reindex_wrapper] ||= ->(&reindex) { reindex.call } # default wrapper
|
242
|
+
end
|
243
|
+
else
|
244
|
+
raise NotImplementedError, "Unsupported strategy: '#{strategy}'"
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
224
249
|
end
|
225
250
|
end
|
226
251
|
end
|
data/lib/chewy/journal.rb
CHANGED
@@ -16,14 +16,17 @@ module Chewy
|
|
16
16
|
# specified indexes.
|
17
17
|
#
|
18
18
|
# @param since_time [Time, DateTime] timestamp from which changes will be applied
|
19
|
-
# @param
|
19
|
+
# @param fetch_limit [Int] amount of entries to be fetched on each cycle
|
20
20
|
# @return [Integer] the amount of journal entries found
|
21
|
-
def apply(since_time,
|
21
|
+
def apply(since_time, fetch_limit: 10, **import_options)
|
22
22
|
stage = 1
|
23
23
|
since_time -= 1
|
24
24
|
count = 0
|
25
|
-
|
26
|
-
|
25
|
+
|
26
|
+
total_count = entries(since_time, fetch_limit).total_count
|
27
|
+
|
28
|
+
while count < total_count
|
29
|
+
entries = entries(since_time, fetch_limit).to_a.presence or break
|
27
30
|
count += entries.size
|
28
31
|
groups = reference_groups(entries)
|
29
32
|
ActiveSupport::Notifications.instrument 'apply_journal.chewy', stage: stage, groups: groups
|
@@ -40,12 +43,20 @@ module Chewy
|
|
40
43
|
#
|
41
44
|
# @param until_time [Time, DateTime] time to clean up until it
|
42
45
|
# @return [Hash] delete_by_query ES API call result
|
43
|
-
def clean(until_time = nil)
|
44
|
-
Chewy::Stash::Journal.clean(
|
46
|
+
def clean(until_time = nil, delete_by_query_options: {})
|
47
|
+
Chewy::Stash::Journal.clean(
|
48
|
+
until_time,
|
49
|
+
only: @only,
|
50
|
+
delete_by_query_options: delete_by_query_options.merge(refresh: false)
|
51
|
+
)
|
45
52
|
end
|
46
53
|
|
47
54
|
private
|
48
55
|
|
56
|
+
def entries(since_time, fetch_limit)
|
57
|
+
Chewy::Stash::Journal.entries(since_time, only: @only).order(:created_at).limit(fetch_limit)
|
58
|
+
end
|
59
|
+
|
49
60
|
def reference_groups(entries)
|
50
61
|
entries.group_by(&:index_name)
|
51
62
|
.transform_keys { |index_name| Chewy.derive_name(index_name) }
|
data/lib/chewy/log_subscriber.rb
CHANGED
@@ -24,7 +24,11 @@ module Chewy
|
|
24
24
|
|
25
25
|
subject = payload[:type].presence || payload[:index]
|
26
26
|
action = "#{subject} #{action} (#{event.duration.round(1)}ms)"
|
27
|
-
action =
|
27
|
+
action = if ActiveSupport.version >= Gem::Version.new('7.1')
|
28
|
+
color(action, GREEN, bold: true)
|
29
|
+
else
|
30
|
+
color(action, GREEN, true)
|
31
|
+
end
|
28
32
|
|
29
33
|
debug(" #{action} #{description}")
|
30
34
|
end
|
@@ -42,10 +42,87 @@ module Chewy
|
|
42
42
|
# Run indexing for the database changes during the block provided.
|
43
43
|
# By default, indexing is run at the end of the block.
|
44
44
|
# @param strategy [Symbol] the Chewy index update strategy see Chewy docs.
|
45
|
+
#
|
45
46
|
def run_indexing(strategy: :atomic, &block)
|
46
47
|
Chewy.strategy strategy, &block
|
47
48
|
end
|
48
49
|
|
50
|
+
# Mock Elasticsearch response
|
51
|
+
# Simple usage - just pass index, expected raw response
|
52
|
+
# and block with the query.
|
53
|
+
#
|
54
|
+
# @param index [Chewy::Index] the index to watch, eg EntitiesIndex.
|
55
|
+
# @param raw_response [Hash] hash with response.
|
56
|
+
#
|
57
|
+
def mock_elasticsearch_response(index, raw_response)
|
58
|
+
mocked_request = Chewy::Search::Request.new(index)
|
59
|
+
|
60
|
+
original_new = Chewy::Search::Request.method(:new)
|
61
|
+
|
62
|
+
Chewy::Search::Request.define_singleton_method(:new) { |*_args| mocked_request }
|
63
|
+
|
64
|
+
original_perform = mocked_request.method(:perform)
|
65
|
+
mocked_request.define_singleton_method(:perform) { raw_response }
|
66
|
+
|
67
|
+
yield
|
68
|
+
ensure
|
69
|
+
mocked_request.define_singleton_method(:perform, original_perform)
|
70
|
+
Chewy::Search::Request.define_singleton_method(:new, original_new)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Mock Elasticsearch response with defined sources
|
74
|
+
# Simple usage - just pass index, expected sources
|
75
|
+
# and block with the query.
|
76
|
+
#
|
77
|
+
# @param index [Chewy::Index] the index to watch, eg EntitiesIndex.
|
78
|
+
# @param hits [Hash] hash with sources.
|
79
|
+
#
|
80
|
+
def mock_elasticsearch_response_sources(index, hits, &block)
|
81
|
+
raw_response = {
|
82
|
+
'took' => 4,
|
83
|
+
'timed_out' => false,
|
84
|
+
'_shards' => {
|
85
|
+
'total' => 1,
|
86
|
+
'successful' => 1,
|
87
|
+
'skipped' => 0,
|
88
|
+
'failed' => 0
|
89
|
+
},
|
90
|
+
'hits' => {
|
91
|
+
'total' => {
|
92
|
+
'value' => hits.count,
|
93
|
+
'relation' => 'eq'
|
94
|
+
},
|
95
|
+
'max_score' => 1.0,
|
96
|
+
'hits' => hits.each_with_index.map do |hit, i|
|
97
|
+
{
|
98
|
+
'_index' => index.index_name,
|
99
|
+
'_type' => '_doc',
|
100
|
+
'_id' => hit[:id] || (i + 1).to_s,
|
101
|
+
'_score' => 3.14,
|
102
|
+
'_source' => hit
|
103
|
+
}
|
104
|
+
end
|
105
|
+
}
|
106
|
+
}
|
107
|
+
|
108
|
+
mock_elasticsearch_response(index, raw_response, &block)
|
109
|
+
end
|
110
|
+
|
111
|
+
# Check the assertion that actual Elasticsearch query is rendered
|
112
|
+
# to the expected query
|
113
|
+
#
|
114
|
+
# @param query [::Query] the actual Elasticsearch query.
|
115
|
+
# @param expected_query [Hash] expected query.
|
116
|
+
#
|
117
|
+
# @return [Boolean]
|
118
|
+
# True - in the case when actual Elasticsearch query is rendered to the expected query.
|
119
|
+
# False - in the opposite case.
|
120
|
+
#
|
121
|
+
def assert_elasticsearch_query(query, expected_query)
|
122
|
+
actual_query = query.render
|
123
|
+
assert_equal expected_query, actual_query, "got #{actual_query.inspect} instead of expected query."
|
124
|
+
end
|
125
|
+
|
49
126
|
module ClassMethods
|
50
127
|
# Declare that all tests in this file require real indexing, always.
|
51
128
|
# In my completely unscientific experiments, this roughly doubled test runtime.
|
@@ -6,6 +6,8 @@
|
|
6
6
|
# The class will capture the data from the *param on the Chewy::Index.bulk method and
|
7
7
|
# aggregate the data for test analysis.
|
8
8
|
class SearchIndexReceiver
|
9
|
+
MUTATION_FOR_CLASS = Struct.new(:indexes, :deletes, keyword_init: true)
|
10
|
+
|
9
11
|
def initialize
|
10
12
|
@mutations = {}
|
11
13
|
end
|
@@ -71,6 +73,6 @@ private
|
|
71
73
|
# @param index [Chewy::Index] the index to fetch.
|
72
74
|
# @return [#indexes, #deletes] an object with a list of indexes and a list of deletes.
|
73
75
|
def mutation_for(index)
|
74
|
-
@mutations[index] ||=
|
76
|
+
@mutations[index] ||= MUTATION_FOR_CLASS.new(indexes: [], deletes: [])
|
75
77
|
end
|
76
78
|
end
|