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
@@ -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
@@ -50,27 +50,30 @@ describe Chewy do
|
|
50
50
|
end
|
51
51
|
|
52
52
|
describe '.client' do
|
53
|
-
let!(:initial_client) {
|
53
|
+
let!(:initial_client) { Chewy.current[:chewy_client] }
|
54
54
|
let(:faraday_block) { proc {} }
|
55
55
|
let(:mock_client) { double(:client) }
|
56
56
|
let(:expected_client_config) { {transport_options: {}} }
|
57
57
|
|
58
58
|
before do
|
59
|
-
|
60
|
-
|
59
|
+
Chewy.current[:chewy_client] = nil
|
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
|
-
after {
|
76
|
+
after { Chewy.current[:chewy_client] = initial_client }
|
74
77
|
end
|
75
78
|
|
76
79
|
describe '.create_indices' do
|
data/spec/spec_helper.rb
CHANGED
@@ -8,9 +8,8 @@ require 'rspec/its'
|
|
8
8
|
require 'rspec/collection_matchers'
|
9
9
|
|
10
10
|
require 'timecop'
|
11
|
-
require 'ruby-progressbar'
|
12
11
|
|
13
|
-
Kaminari::Hooks.init if defined?(
|
12
|
+
Kaminari::Hooks.init if defined?(Kaminari::Hooks)
|
14
13
|
|
15
14
|
require 'support/fail_helpers'
|
16
15
|
require 'support/class_helpers'
|
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'database_cleaner'
|
2
2
|
|
3
|
-
ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: '
|
3
|
+
ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:', pool: 10)
|
4
4
|
ActiveRecord::Base.logger = Logger.new('/dev/null')
|
5
5
|
if ActiveRecord::Base.respond_to?(:raise_in_transactional_callbacks)
|
6
6
|
ActiveRecord::Base.raise_in_transactional_callbacks = true
|
@@ -31,6 +31,13 @@ ActiveRecord::Schema.define do
|
|
31
31
|
t.column :lat, :string
|
32
32
|
t.column :lon, :string
|
33
33
|
end
|
34
|
+
|
35
|
+
create_table :comments do |t|
|
36
|
+
t.column :content, :string
|
37
|
+
t.column :comment_type, :string
|
38
|
+
t.column :commented_id, :integer
|
39
|
+
t.column :updated_at, :datetime
|
40
|
+
end
|
34
41
|
end
|
35
42
|
|
36
43
|
module ActiveRecordClassHelpers
|