chewy 7.2.4 → 7.6.0
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.
- checksums.yaml +4 -4
- data/.github/CODEOWNERS +1 -0
- data/.github/dependabot.yml +42 -0
- data/.github/workflows/ruby.yml +26 -32
- data/.rubocop.yml +4 -1
- data/CHANGELOG.md +144 -0
- data/Gemfile +4 -4
- data/README.md +165 -10
- data/chewy.gemspec +4 -17
- data/gemfiles/base.gemfile +12 -0
- data/gemfiles/rails.6.1.activerecord.gemfile +2 -1
- data/gemfiles/rails.7.0.activerecord.gemfile +2 -1
- data/gemfiles/{rails.5.2.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 +5 -2
- data/lib/chewy/fields/base.rb +1 -1
- data/lib/chewy/fields/root.rb +1 -1
- data/lib/chewy/index/adapter/active_record.rb +13 -3
- data/lib/chewy/index/adapter/object.rb +3 -3
- data/lib/chewy/index/adapter/orm.rb +2 -2
- data/lib/chewy/index/crutch.rb +15 -7
- data/lib/chewy/index/import/bulk_builder.rb +6 -7
- data/lib/chewy/index/import/routine.rb +1 -1
- data/lib/chewy/index/import.rb +31 -4
- 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 +1 -1
- data/lib/chewy/minitest/search_index_receiver.rb +3 -1
- data/lib/chewy/rake_helper.rb +74 -13
- data/lib/chewy/rspec/update_index.rb +13 -6
- data/lib/chewy/runtime/version.rb +1 -1
- data/lib/chewy/search/parameters/collapse.rb +16 -0
- data/lib/chewy/search/parameters/indices.rb +1 -1
- data/lib/chewy/search/parameters/knn.rb +16 -0
- data/lib/chewy/search/parameters/storage.rb +1 -1
- data/lib/chewy/search/parameters.rb +3 -3
- data/lib/chewy/search/request.rb +45 -11
- data/lib/chewy/search.rb +6 -3
- data/lib/chewy/stash.rb +3 -3
- 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.rb +3 -0
- data/lib/chewy/version.rb +1 -1
- data/lib/chewy.rb +5 -8
- data/lib/tasks/chewy.rake +17 -1
- 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 +1 -0
- data/spec/chewy/index/actions_spec.rb +4 -4
- data/spec/chewy/index/adapter/active_record_spec.rb +62 -0
- data/spec/chewy/index/import/bulk_builder_spec.rb +7 -3
- data/spec/chewy/index/import_spec.rb +16 -3
- 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 +3 -3
- data/spec/chewy/minitest/search_index_receiver_spec.rb +6 -4
- data/spec/chewy/rake_helper_spec.rb +155 -4
- data/spec/chewy/rspec/helpers_spec.rb +1 -1
- 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/knn_spec.rb +5 -0
- data/spec/chewy/search/request_spec.rb +37 -0
- 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 +7 -4
- data/spec/spec_helper.rb +1 -1
- metadata +32 -253
- data/gemfiles/rails.6.0.activerecord.gemfile +0 -11
|
@@ -314,6 +314,18 @@ describe Chewy::Search::Request do
|
|
|
314
314
|
end
|
|
315
315
|
end
|
|
316
316
|
|
|
317
|
+
%i[collapse knn].each do |name|
|
|
318
|
+
describe "##{name}" do
|
|
319
|
+
specify { expect(subject.send(name, foo: {bar: 42}).render[:body]).to include(name => {'foo' => {bar: 42}}) }
|
|
320
|
+
specify do
|
|
321
|
+
expect(subject.send(name, foo: {bar: 42}).send(name, moo: {baz: 43}).render[:body])
|
|
322
|
+
.to include(name => {'moo' => {baz: 43}})
|
|
323
|
+
end
|
|
324
|
+
specify { expect(subject.send(name, foo: {bar: 42}).send(name, nil).render[:body]).to be_blank }
|
|
325
|
+
specify { expect { subject.send(name, foo: {bar: 42}) }.not_to change { subject.render } }
|
|
326
|
+
end
|
|
327
|
+
end
|
|
328
|
+
|
|
317
329
|
describe '#docvalue_fields' do
|
|
318
330
|
specify { expect(subject.docvalue_fields(:foo).render[:body]).to include(docvalue_fields: ['foo']) }
|
|
319
331
|
specify do
|
|
@@ -807,6 +819,31 @@ describe Chewy::Search::Request do
|
|
|
807
819
|
request: {index: ['products'], body: {query: {match: {name: 'name3'}}}, refresh: false}
|
|
808
820
|
)
|
|
809
821
|
end
|
|
822
|
+
|
|
823
|
+
it 'delete records asynchronously' do
|
|
824
|
+
outer_payload = nil
|
|
825
|
+
ActiveSupport::Notifications.subscribe('delete_query.chewy') do |_name, _start, _finish, _id, payload|
|
|
826
|
+
outer_payload = payload
|
|
827
|
+
end
|
|
828
|
+
subject.query(match: {name: 'name3'}).delete_all(
|
|
829
|
+
refresh: false,
|
|
830
|
+
wait_for_completion: false,
|
|
831
|
+
requests_per_second: 10.0,
|
|
832
|
+
scroll_size: 2000
|
|
833
|
+
)
|
|
834
|
+
expect(outer_payload).to eq(
|
|
835
|
+
index: ProductsIndex,
|
|
836
|
+
indexes: [ProductsIndex],
|
|
837
|
+
request: {
|
|
838
|
+
index: ['products'],
|
|
839
|
+
body: {query: {match: {name: 'name3'}}},
|
|
840
|
+
refresh: false,
|
|
841
|
+
wait_for_completion: false,
|
|
842
|
+
requests_per_second: 10.0,
|
|
843
|
+
scroll_size: 2000
|
|
844
|
+
}
|
|
845
|
+
)
|
|
846
|
+
end
|
|
810
847
|
end
|
|
811
848
|
|
|
812
849
|
describe '#response=' do
|
data/spec/chewy/search_spec.rb
CHANGED
|
@@ -48,6 +48,10 @@ describe Chewy::Search do
|
|
|
48
48
|
filter { match name: "Name#{index}" }
|
|
49
49
|
end
|
|
50
50
|
|
|
51
|
+
def self.by_rating_with_kwargs(value, options:) # rubocop:disable Lint/UnusedMethodArgument
|
|
52
|
+
filter { match rating: value }
|
|
53
|
+
end
|
|
54
|
+
|
|
51
55
|
index_scope City
|
|
52
56
|
field :name, type: 'keyword'
|
|
53
57
|
field :rating, type: :integer
|
|
@@ -114,5 +118,10 @@ describe Chewy::Search do
|
|
|
114
118
|
specify { expect(CountriesIndex.by_rating(3).by_name(5).map(&:class)).to eq([CountriesIndex]) }
|
|
115
119
|
specify { expect(CountriesIndex.order(:name).by_rating(3).map(&:rating)).to eq([3]) }
|
|
116
120
|
specify { expect(CountriesIndex.order(:name).by_rating(3).map(&:class)).to eq([CountriesIndex]) }
|
|
121
|
+
|
|
122
|
+
specify 'supports keyword arguments' do
|
|
123
|
+
expect(CitiesIndex.by_rating_with_kwargs(3, options: 'blah blah blah').map(&:rating)).to eq([3])
|
|
124
|
+
expect(CitiesIndex.order(:name).by_rating_with_kwargs(3, options: 'blah blah blah').map(&:rating)).to eq([3])
|
|
125
|
+
end
|
|
117
126
|
end
|
|
118
127
|
end
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
require 'spec_helper'
|
|
2
2
|
|
|
3
|
-
if defined?(
|
|
3
|
+
if defined?(ActiveJob)
|
|
4
4
|
describe Chewy::Strategy::ActiveJob do
|
|
5
5
|
around do |example|
|
|
6
6
|
active_job_settings = Chewy.settings[:active_job]
|
|
@@ -9,12 +9,12 @@ if defined?(::ActiveJob)
|
|
|
9
9
|
Chewy.settings[:active_job] = active_job_settings
|
|
10
10
|
end
|
|
11
11
|
before(:all) do
|
|
12
|
-
|
|
12
|
+
ActiveJob::Base.logger = Chewy.logger
|
|
13
13
|
end
|
|
14
14
|
before do
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
ActiveJob::Base.queue_adapter = :test
|
|
16
|
+
ActiveJob::Base.queue_adapter.enqueued_jobs.clear
|
|
17
|
+
ActiveJob::Base.queue_adapter.performed_jobs.clear
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
before do
|
|
@@ -39,7 +39,7 @@ if defined?(::ActiveJob)
|
|
|
39
39
|
Chewy.strategy(:active_job) do
|
|
40
40
|
[city, other_city].map(&:save!)
|
|
41
41
|
end
|
|
42
|
-
enqueued_job =
|
|
42
|
+
enqueued_job = ActiveJob::Base.queue_adapter.enqueued_jobs.first
|
|
43
43
|
expect(enqueued_job[:job]).to eq(Chewy::Strategy::ActiveJob::Worker)
|
|
44
44
|
expect(enqueued_job[:queue]).to eq('low')
|
|
45
45
|
end
|
|
@@ -48,12 +48,12 @@ if defined?(::ActiveJob)
|
|
|
48
48
|
Chewy.strategy(:active_job) do
|
|
49
49
|
[city, other_city].map(&:save!)
|
|
50
50
|
end
|
|
51
|
-
enqueued_job =
|
|
51
|
+
enqueued_job = ActiveJob::Base.queue_adapter.enqueued_jobs.first
|
|
52
52
|
expect(enqueued_job[:queue]).to eq('low')
|
|
53
53
|
end
|
|
54
54
|
|
|
55
55
|
specify do
|
|
56
|
-
|
|
56
|
+
ActiveJob::Base.queue_adapter = :inline
|
|
57
57
|
expect { [city, other_city].map(&:save!) }
|
|
58
58
|
.to update_index(CitiesIndex, strategy: :active_job)
|
|
59
59
|
.and_reindex(city, other_city).only
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Chewy::Strategy::AtomicNoRefresh, :orm do
|
|
4
|
+
around { |example| Chewy.strategy(:bypass) { example.run } }
|
|
5
|
+
|
|
6
|
+
before do
|
|
7
|
+
stub_model(:country) do
|
|
8
|
+
update_index('countries') { self }
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
stub_index(:countries) do
|
|
12
|
+
index_scope Country
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
let(:country) { Country.create!(name: 'hello', country_code: 'HL') }
|
|
17
|
+
let(:other_country) { Country.create!(name: 'world', country_code: 'WD') }
|
|
18
|
+
|
|
19
|
+
specify do
|
|
20
|
+
expect { [country, other_country].map(&:save!) }
|
|
21
|
+
.to update_index(CountriesIndex, strategy: :atomic_no_refresh)
|
|
22
|
+
.and_reindex(country, other_country).only.no_refresh
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
specify do
|
|
26
|
+
expect { [country, other_country].map(&:destroy) }
|
|
27
|
+
.to update_index(CountriesIndex, strategy: :atomic_no_refresh)
|
|
28
|
+
.and_delete(country, other_country).only.no_refresh
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
context do
|
|
32
|
+
before do
|
|
33
|
+
stub_index(:countries) do
|
|
34
|
+
index_scope Country
|
|
35
|
+
root id: -> { country_code }
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
specify do
|
|
40
|
+
expect { [country, other_country].map(&:save!) }
|
|
41
|
+
.to update_index(CountriesIndex, strategy: :atomic_no_refresh)
|
|
42
|
+
.and_reindex('HL', 'WD').only.no_refresh
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
specify do
|
|
46
|
+
expect { [country, other_country].map(&:destroy) }
|
|
47
|
+
.to update_index(CountriesIndex, strategy: :atomic_no_refresh)
|
|
48
|
+
.and_delete('HL', 'WD').only.no_refresh
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
specify do
|
|
52
|
+
expect do
|
|
53
|
+
country.save!
|
|
54
|
+
other_country.destroy
|
|
55
|
+
end
|
|
56
|
+
.to update_index(CountriesIndex, strategy: :atomic_no_refresh)
|
|
57
|
+
.and_reindex('HL').and_delete('WD').only.no_refresh
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
if defined?(Sidekiq)
|
|
4
|
+
require 'sidekiq/testing'
|
|
5
|
+
require 'redis'
|
|
6
|
+
|
|
7
|
+
describe Chewy::Strategy::DelayedSidekiq do
|
|
8
|
+
around do |example|
|
|
9
|
+
Chewy.strategy(:bypass) { example.run }
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
before do
|
|
13
|
+
redis = Redis.new
|
|
14
|
+
allow(Sidekiq).to receive(:redis).and_yield(redis)
|
|
15
|
+
Sidekiq::Worker.clear_all
|
|
16
|
+
described_class.clear_timechunks!
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
before do
|
|
20
|
+
stub_model(:city) do
|
|
21
|
+
update_index('cities') { self }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
stub_index(:cities) do
|
|
25
|
+
index_scope City
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
let(:city) { City.create!(name: 'hello') }
|
|
30
|
+
let(:other_city) { City.create!(name: 'world') }
|
|
31
|
+
|
|
32
|
+
it 'does not trigger immediate reindex due to it`s async nature' do
|
|
33
|
+
expect { [city, other_city].map(&:save!) }
|
|
34
|
+
.not_to update_index(CitiesIndex, strategy: :delayed_sidekiq)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it "respects 'refresh: false' options" do
|
|
38
|
+
allow(Chewy).to receive(:disable_refresh_async).and_return(true)
|
|
39
|
+
expect(CitiesIndex).to receive(:import!).with(match_array([city.id, other_city.id]), refresh: false)
|
|
40
|
+
scheduler = Chewy::Strategy::DelayedSidekiq::Scheduler.new(CitiesIndex, [city.id, other_city.id])
|
|
41
|
+
scheduler.postpone
|
|
42
|
+
Chewy::Strategy::DelayedSidekiq::Worker.drain
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
context 'with default config' do
|
|
46
|
+
it 'does schedule a job that triggers reindex with default options' do
|
|
47
|
+
Timecop.freeze do
|
|
48
|
+
expect(Sidekiq::Client).to receive(:push).with(
|
|
49
|
+
hash_including(
|
|
50
|
+
'queue' => 'chewy',
|
|
51
|
+
'at' => expected_at_time.to_i,
|
|
52
|
+
'class' => Chewy::Strategy::DelayedSidekiq::Worker,
|
|
53
|
+
'args' => ['CitiesIndex', an_instance_of(Integer)]
|
|
54
|
+
)
|
|
55
|
+
).and_call_original
|
|
56
|
+
|
|
57
|
+
expect($stdout).not_to receive(:puts)
|
|
58
|
+
|
|
59
|
+
Sidekiq::Testing.inline! do
|
|
60
|
+
expect { [city, other_city].map(&:save!) }
|
|
61
|
+
.to update_index(CitiesIndex, strategy: :delayed_sidekiq)
|
|
62
|
+
.and_reindex(city, other_city).only
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def expected_at_time
|
|
68
|
+
target = described_class::Scheduler::DEFAULT_LATENCY.seconds.from_now.to_i
|
|
69
|
+
target - (target % described_class::Scheduler::DEFAULT_LATENCY) + described_class::Scheduler::DEFAULT_MARGIN.seconds
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
context 'with custom config' do
|
|
74
|
+
before do
|
|
75
|
+
CitiesIndex.strategy_config(
|
|
76
|
+
delayed_sidekiq: {
|
|
77
|
+
reindex_wrapper: lambda { |&reindex|
|
|
78
|
+
puts 'hello'
|
|
79
|
+
reindex.call
|
|
80
|
+
},
|
|
81
|
+
margin: 5,
|
|
82
|
+
latency: 60
|
|
83
|
+
}
|
|
84
|
+
)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
it 'respects :strategy_config options' do
|
|
88
|
+
Timecop.freeze do
|
|
89
|
+
expect(Sidekiq::Client).to receive(:push).with(
|
|
90
|
+
hash_including(
|
|
91
|
+
'queue' => 'chewy',
|
|
92
|
+
'at' => (60.seconds.from_now.change(sec: 0) + 5.seconds).to_i,
|
|
93
|
+
'class' => Chewy::Strategy::DelayedSidekiq::Worker,
|
|
94
|
+
'args' => ['CitiesIndex', an_instance_of(Integer)]
|
|
95
|
+
)
|
|
96
|
+
).and_call_original
|
|
97
|
+
|
|
98
|
+
expect($stdout).to receive(:puts).with('hello') # check that reindex_wrapper works
|
|
99
|
+
|
|
100
|
+
Sidekiq::Testing.inline! do
|
|
101
|
+
expect { [city, other_city].map(&:save!) }
|
|
102
|
+
.to update_index(CitiesIndex, strategy: :delayed_sidekiq)
|
|
103
|
+
.and_reindex(city, other_city).only
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
context 'two reindex call within the timewindow' do
|
|
110
|
+
it 'accumulates all ids does the reindex one time' do
|
|
111
|
+
Timecop.freeze do
|
|
112
|
+
expect(CitiesIndex).to receive(:import!).with(match_array([city.id, other_city.id])).once
|
|
113
|
+
scheduler = Chewy::Strategy::DelayedSidekiq::Scheduler.new(CitiesIndex, [city.id])
|
|
114
|
+
scheduler.postpone
|
|
115
|
+
scheduler = Chewy::Strategy::DelayedSidekiq::Scheduler.new(CitiesIndex, [other_city.id])
|
|
116
|
+
scheduler.postpone
|
|
117
|
+
Chewy::Strategy::DelayedSidekiq::Worker.drain
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
context 'one call with update_fields another one without update_fields' do
|
|
122
|
+
it 'does reindex of all fields' do
|
|
123
|
+
Timecop.freeze do
|
|
124
|
+
expect(CitiesIndex).to receive(:import!).with(match_array([city.id, other_city.id])).once
|
|
125
|
+
scheduler = Chewy::Strategy::DelayedSidekiq::Scheduler.new(CitiesIndex, [city.id], update_fields: ['name'])
|
|
126
|
+
scheduler.postpone
|
|
127
|
+
scheduler = Chewy::Strategy::DelayedSidekiq::Scheduler.new(CitiesIndex, [other_city.id])
|
|
128
|
+
scheduler.postpone
|
|
129
|
+
Chewy::Strategy::DelayedSidekiq::Worker.drain
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
context 'both calls with different update fields' do
|
|
135
|
+
it 'deos reindex with union of fields' do
|
|
136
|
+
Timecop.freeze do
|
|
137
|
+
expect(CitiesIndex).to receive(:import!).with(match_array([city.id, other_city.id]), update_fields: %w[name description]).once
|
|
138
|
+
scheduler = Chewy::Strategy::DelayedSidekiq::Scheduler.new(CitiesIndex, [city.id], update_fields: ['name'])
|
|
139
|
+
scheduler.postpone
|
|
140
|
+
scheduler = Chewy::Strategy::DelayedSidekiq::Scheduler.new(CitiesIndex, [other_city.id], update_fields: ['description'])
|
|
141
|
+
scheduler.postpone
|
|
142
|
+
Chewy::Strategy::DelayedSidekiq::Worker.drain
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
context 'two calls within different timewindows' do
|
|
149
|
+
it 'does two separate reindexes' do
|
|
150
|
+
Timecop.freeze do
|
|
151
|
+
expect(CitiesIndex).to receive(:import!).with([city.id]).once
|
|
152
|
+
expect(CitiesIndex).to receive(:import!).with([other_city.id]).once
|
|
153
|
+
Timecop.travel(20.seconds.ago) do
|
|
154
|
+
scheduler = Chewy::Strategy::DelayedSidekiq::Scheduler.new(CitiesIndex, [city.id])
|
|
155
|
+
scheduler.postpone
|
|
156
|
+
end
|
|
157
|
+
scheduler = Chewy::Strategy::DelayedSidekiq::Scheduler.new(CitiesIndex, [other_city.id])
|
|
158
|
+
scheduler.postpone
|
|
159
|
+
Chewy::Strategy::DelayedSidekiq::Worker.drain
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
context 'first call has update_fields' do
|
|
165
|
+
it 'does first reindex with the expected update_fields and second without update_fields' do
|
|
166
|
+
Timecop.freeze do
|
|
167
|
+
expect(CitiesIndex).to receive(:import!).with([city.id], update_fields: ['name']).once
|
|
168
|
+
expect(CitiesIndex).to receive(:import!).with([other_city.id]).once
|
|
169
|
+
Timecop.travel(20.seconds.ago) do
|
|
170
|
+
scheduler = Chewy::Strategy::DelayedSidekiq::Scheduler.new(CitiesIndex, [city.id], update_fields: ['name'])
|
|
171
|
+
scheduler.postpone
|
|
172
|
+
end
|
|
173
|
+
scheduler = Chewy::Strategy::DelayedSidekiq::Scheduler.new(CitiesIndex, [other_city.id])
|
|
174
|
+
scheduler.postpone
|
|
175
|
+
Chewy::Strategy::DelayedSidekiq::Worker.drain
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
context 'both calls have update_fields option' do
|
|
181
|
+
it 'does both reindexes with their expected update_fields option' do
|
|
182
|
+
Timecop.freeze do
|
|
183
|
+
expect(CitiesIndex).to receive(:import!).with([city.id], update_fields: ['name']).once
|
|
184
|
+
expect(CitiesIndex).to receive(:import!).with([other_city.id], update_fields: ['description']).once
|
|
185
|
+
Timecop.travel(20.seconds.ago) do
|
|
186
|
+
scheduler = Chewy::Strategy::DelayedSidekiq::Scheduler.new(CitiesIndex, [city.id], update_fields: ['name'])
|
|
187
|
+
scheduler.postpone
|
|
188
|
+
end
|
|
189
|
+
scheduler = Chewy::Strategy::DelayedSidekiq::Scheduler.new(CitiesIndex, [other_city.id], update_fields: ['description'])
|
|
190
|
+
scheduler.postpone
|
|
191
|
+
Chewy::Strategy::DelayedSidekiq::Worker.drain
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
describe '#clear_delayed_sidekiq_timechunks test helper' do
|
|
197
|
+
it 'clears redis from the timechunk sorted sets to avoid leak between tests' do
|
|
198
|
+
timechunks_set = -> { Sidekiq.redis { |redis| redis.zrange('chewy:delayed_sidekiq:CitiesIndex:timechunks', 0, -1) } }
|
|
199
|
+
|
|
200
|
+
expect { CitiesIndex.import!([1], strategy: :delayed_sidekiq) }
|
|
201
|
+
.to change { timechunks_set.call.size }.by(1)
|
|
202
|
+
|
|
203
|
+
expect { Chewy::Strategy::DelayedSidekiq.clear_timechunks! }
|
|
204
|
+
.to change { timechunks_set.call.size }.to(0)
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
end
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
if defined?(Sidekiq)
|
|
4
|
+
require 'sidekiq/testing'
|
|
5
|
+
|
|
6
|
+
describe Chewy::Strategy::LazySidekiq do
|
|
7
|
+
around do |example|
|
|
8
|
+
sidekiq_settings = Chewy.settings[:sidekiq]
|
|
9
|
+
Chewy.settings[:sidekiq] = {queue: 'low'}
|
|
10
|
+
Chewy.strategy(:bypass) { example.run }
|
|
11
|
+
Chewy.settings[:sidekiq] = sidekiq_settings
|
|
12
|
+
end
|
|
13
|
+
before { Sidekiq::Worker.clear_all }
|
|
14
|
+
|
|
15
|
+
context 'strategy' do
|
|
16
|
+
before do
|
|
17
|
+
stub_model(:city) do
|
|
18
|
+
update_index('cities') { self }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
stub_index(:cities) do
|
|
22
|
+
index_scope City
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
let(:city) { City.create!(name: 'hello') }
|
|
27
|
+
let(:other_city) { City.create!(name: 'world') }
|
|
28
|
+
|
|
29
|
+
it 'does not update indices synchronously' do
|
|
30
|
+
expect { [city, other_city].map(&:save!) }
|
|
31
|
+
.not_to update_index(CitiesIndex, strategy: :lazy_sidekiq)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it 'updates indices asynchronously on record save' do
|
|
35
|
+
expect(Sidekiq::Client).to receive(:push)
|
|
36
|
+
.with(hash_including(
|
|
37
|
+
'class' => Chewy::Strategy::LazySidekiq::IndicesUpdateWorker,
|
|
38
|
+
'queue' => 'low'
|
|
39
|
+
))
|
|
40
|
+
.and_call_original
|
|
41
|
+
.once
|
|
42
|
+
Sidekiq::Testing.inline! do
|
|
43
|
+
expect { [city, other_city].map(&:save!) }
|
|
44
|
+
.to update_index(CitiesIndex, strategy: :lazy_sidekiq)
|
|
45
|
+
.and_reindex(city, other_city).only
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
it 'updates indices asynchronously with falling back to sidekiq strategy on record destroy' do
|
|
50
|
+
expect(Sidekiq::Client).not_to receive(:push)
|
|
51
|
+
.with(hash_including(
|
|
52
|
+
'class' => Chewy::Strategy::LazySidekiq::IndicesUpdateWorker,
|
|
53
|
+
'queue' => 'low'
|
|
54
|
+
))
|
|
55
|
+
expect(Sidekiq::Client).to receive(:push)
|
|
56
|
+
.with(hash_including(
|
|
57
|
+
'class' => Chewy::Strategy::Sidekiq::Worker,
|
|
58
|
+
'queue' => 'low',
|
|
59
|
+
'args' => ['CitiesIndex', [city.id, other_city.id]]
|
|
60
|
+
))
|
|
61
|
+
.and_call_original
|
|
62
|
+
.once
|
|
63
|
+
Sidekiq::Testing.inline! do
|
|
64
|
+
expect { [city, other_city].map(&:destroy) }.to update_index(CitiesIndex, strategy: :sidekiq)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
it 'calls Index#import!' do
|
|
69
|
+
allow(City).to receive(:where).with(id: [city.id, other_city.id]).and_return([city, other_city])
|
|
70
|
+
expect(city).to receive(:run_chewy_callbacks).and_call_original
|
|
71
|
+
expect(other_city).to receive(:run_chewy_callbacks).and_call_original
|
|
72
|
+
|
|
73
|
+
expect do
|
|
74
|
+
Sidekiq::Testing.inline! do
|
|
75
|
+
Chewy::Strategy::LazySidekiq::IndicesUpdateWorker.new.perform({'City' => [city.id, other_city.id]})
|
|
76
|
+
end
|
|
77
|
+
end.to update_index(CitiesIndex).and_reindex(city, other_city).only
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
context 'when Chewy.disable_refresh_async is true' do
|
|
81
|
+
before do
|
|
82
|
+
allow(Chewy).to receive(:disable_refresh_async).and_return(true)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
it 'calls Index#import! with refresh false' do
|
|
86
|
+
allow(City).to receive(:where).with(id: [city.id, other_city.id]).and_return([city, other_city])
|
|
87
|
+
expect(city).to receive(:run_chewy_callbacks).and_call_original
|
|
88
|
+
expect(other_city).to receive(:run_chewy_callbacks).and_call_original
|
|
89
|
+
|
|
90
|
+
expect do
|
|
91
|
+
Sidekiq::Testing.inline! do
|
|
92
|
+
Chewy::Strategy::LazySidekiq::IndicesUpdateWorker.new.perform({'City' => [city.id, other_city.id]})
|
|
93
|
+
end
|
|
94
|
+
end.to update_index(CitiesIndex).and_reindex(city, other_city).only.no_refresh
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
context 'integration' do
|
|
100
|
+
around { |example| Sidekiq::Testing.inline! { example.run } }
|
|
101
|
+
|
|
102
|
+
let(:update_condition) { true }
|
|
103
|
+
|
|
104
|
+
before do
|
|
105
|
+
city_model
|
|
106
|
+
country_model
|
|
107
|
+
|
|
108
|
+
City.belongs_to :country
|
|
109
|
+
Country.has_many :cities
|
|
110
|
+
|
|
111
|
+
stub_index(:cities) do
|
|
112
|
+
index_scope City
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
stub_index(:countries) do
|
|
116
|
+
index_scope Country
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
context 'state dependent' do
|
|
121
|
+
let(:city_model) do
|
|
122
|
+
stub_model(:city) do
|
|
123
|
+
update_index(-> { 'cities' }, :self)
|
|
124
|
+
update_index('countries') { changes['country_id'] || previous_changes['country_id'] || country }
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
let(:country_model) do
|
|
129
|
+
stub_model(:country) do
|
|
130
|
+
update_index('cities', if: -> { state_dependent_update_condition }) { cities }
|
|
131
|
+
update_index(-> { 'countries' }, :self)
|
|
132
|
+
attr_accessor :state_dependent_update_condition
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
context 'city updates' do
|
|
137
|
+
let!(:country1) { Country.create!(id: 1) }
|
|
138
|
+
let!(:country2) { Country.create!(id: 2) }
|
|
139
|
+
let!(:city) { City.create!(id: 1, country: country1) }
|
|
140
|
+
|
|
141
|
+
it 'does not update index of removed entity because model state on the moment of save cannot be fetched' do
|
|
142
|
+
expect { city.update!(country: nil) }.not_to update_index('countries', strategy: :lazy_sidekiq)
|
|
143
|
+
end
|
|
144
|
+
it 'does not update index of removed entity because model state on the moment of save cannot be fetched' do
|
|
145
|
+
expect { city.update!(country: country2) }.to update_index('countries', strategy: :lazy_sidekiq).and_reindex(country2).only
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
context 'country updates' do
|
|
150
|
+
let!(:country) do
|
|
151
|
+
cities = Array.new(2) { |i| City.create!(id: i) }
|
|
152
|
+
Country.create!(id: 1, cities: cities, state_dependent_update_condition: update_condition)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
it 'does not update index because state of attribute cannot be fetched' do
|
|
156
|
+
expect { country.save! }.not_to update_index('cities', strategy: :lazy_sidekiq)
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
context 'state independent' do
|
|
162
|
+
let(:city_model) do
|
|
163
|
+
stub_model(:city) do
|
|
164
|
+
update_index(-> { 'cities' }, :self)
|
|
165
|
+
update_index('countries') { country }
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
let(:country_model) do
|
|
170
|
+
stub_model(:country) do
|
|
171
|
+
update_index('cities', if: -> { state_independent_update_condition }) { cities }
|
|
172
|
+
update_index(-> { 'countries' }, :self)
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
before do
|
|
177
|
+
allow_any_instance_of(Country).to receive(:state_independent_update_condition).and_return(update_condition)
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
context 'when city updates' do
|
|
181
|
+
let!(:country1) { Country.create!(id: 1) }
|
|
182
|
+
let!(:country2) { Country.create!(id: 2) }
|
|
183
|
+
let!(:city) { City.create!(id: 1, country: country1) }
|
|
184
|
+
|
|
185
|
+
specify { expect { city.save! }.to update_index('cities', strategy: :lazy_sidekiq).and_reindex(city).only }
|
|
186
|
+
specify { expect { city.save! }.to update_index('countries', strategy: :lazy_sidekiq).and_reindex(country1).only }
|
|
187
|
+
|
|
188
|
+
specify { expect { city.destroy }.not_to update_index('cities').and_reindex(city).only }
|
|
189
|
+
specify { expect { city.destroy }.to update_index('countries', strategy: :sidekiq).and_reindex(country1).only }
|
|
190
|
+
|
|
191
|
+
specify { expect { city.update!(country: nil) }.to update_index('cities', strategy: :lazy_sidekiq).and_reindex(city).only }
|
|
192
|
+
specify { expect { city.update!(country: country2) }.to update_index('cities', strategy: :lazy_sidekiq).and_reindex(city).only }
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
context 'when country updates' do
|
|
196
|
+
let!(:country) do
|
|
197
|
+
cities = Array.new(2) { |i| City.create!(id: i) }
|
|
198
|
+
Country.create!(id: 1, cities: cities)
|
|
199
|
+
end
|
|
200
|
+
specify { expect { country.save! }.to update_index('cities', strategy: :lazy_sidekiq).and_reindex(country.cities).only }
|
|
201
|
+
specify { expect { country.save! }.to update_index('countries', strategy: :lazy_sidekiq).and_reindex(country).only }
|
|
202
|
+
|
|
203
|
+
specify { expect { country.destroy }.to update_index('cities', strategy: :sidekiq).and_reindex(country.cities).only }
|
|
204
|
+
specify { expect { country.destroy }.not_to update_index('countries').and_reindex(country).only }
|
|
205
|
+
|
|
206
|
+
context 'when update condition is false' do
|
|
207
|
+
let(:update_condition) { false }
|
|
208
|
+
specify { expect { country.save! }.not_to update_index('cities', strategy: :lazy_sidekiq) }
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
end
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
require 'spec_helper'
|
|
2
2
|
|
|
3
|
-
if defined?(
|
|
3
|
+
if defined?(Sidekiq)
|
|
4
4
|
require 'sidekiq/testing'
|
|
5
5
|
|
|
6
6
|
describe Chewy::Strategy::Sidekiq do
|
|
@@ -10,7 +10,7 @@ if defined?(::Sidekiq)
|
|
|
10
10
|
Chewy.strategy(:bypass) { example.run }
|
|
11
11
|
Chewy.settings[:sidekiq] = sidekiq_settings
|
|
12
12
|
end
|
|
13
|
-
before {
|
|
13
|
+
before { Sidekiq::Worker.clear_all }
|
|
14
14
|
before do
|
|
15
15
|
stub_model(:city) do
|
|
16
16
|
update_index('cities') { self }
|
|
@@ -30,8 +30,8 @@ if defined?(::Sidekiq)
|
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
specify do
|
|
33
|
-
expect(
|
|
34
|
-
|
|
33
|
+
expect(Sidekiq::Client).to receive(:push).with(hash_including('queue' => 'low')).and_call_original
|
|
34
|
+
Sidekiq::Testing.inline! do
|
|
35
35
|
expect { [city, other_city].map(&:save!) }
|
|
36
36
|
.to update_index(CitiesIndex, strategy: :sidekiq)
|
|
37
37
|
.and_reindex(city, other_city).only
|
data/spec/chewy_spec.rb
CHANGED
|
@@ -57,18 +57,21 @@ describe Chewy do
|
|
|
57
57
|
|
|
58
58
|
before do
|
|
59
59
|
Chewy.current[:chewy_client] = nil
|
|
60
|
-
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
specify do
|
|
63
|
+
expect(Chewy).to receive_messages(configuration: {transport_options: {proc: faraday_block}})
|
|
61
64
|
|
|
62
|
-
|
|
65
|
+
expect(Elasticsearch::Client).to receive(:new).with(expected_client_config) do |*_args, &passed_block|
|
|
63
66
|
# RSpec's `with(..., &block)` was used previously, but doesn't actually do
|
|
64
67
|
# any verification of the passed block (even of its presence).
|
|
65
68
|
expect(passed_block.source_location).to eq(faraday_block.source_location)
|
|
66
69
|
|
|
67
70
|
mock_client
|
|
68
71
|
end
|
|
69
|
-
end
|
|
70
72
|
|
|
71
|
-
|
|
73
|
+
expect(Chewy.client).to be_a(Chewy::ElasticClient)
|
|
74
|
+
end
|
|
72
75
|
|
|
73
76
|
after { Chewy.current[:chewy_client] = initial_client }
|
|
74
77
|
end
|
data/spec/spec_helper.rb
CHANGED