chewy 7.2.1 → 7.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (108) hide show
  1. checksums.yaml +4 -4
  2. data/.github/CODEOWNERS +1 -0
  3. data/.github/dependabot.yml +42 -0
  4. data/.github/workflows/ruby.yml +28 -26
  5. data/.rubocop.yml +4 -1
  6. data/CHANGELOG.md +196 -0
  7. data/Gemfile +4 -3
  8. data/README.md +203 -20
  9. data/chewy.gemspec +4 -18
  10. data/gemfiles/base.gemfile +12 -0
  11. data/gemfiles/rails.6.1.activerecord.gemfile +2 -1
  12. data/gemfiles/{rails.5.2.activerecord.gemfile → rails.7.0.activerecord.gemfile} +6 -3
  13. data/gemfiles/{rails.6.0.activerecord.gemfile → rails.7.1.activerecord.gemfile} +6 -3
  14. data/lib/chewy/config.rb +22 -14
  15. data/lib/chewy/elastic_client.rb +31 -0
  16. data/lib/chewy/errors.rb +11 -2
  17. data/lib/chewy/fields/base.rb +69 -13
  18. data/lib/chewy/fields/root.rb +2 -10
  19. data/lib/chewy/index/actions.rb +11 -16
  20. data/lib/chewy/index/adapter/active_record.rb +18 -3
  21. data/lib/chewy/index/adapter/object.rb +0 -10
  22. data/lib/chewy/index/adapter/orm.rb +4 -14
  23. data/lib/chewy/index/crutch.rb +15 -7
  24. data/lib/chewy/index/import/bulk_builder.rb +219 -32
  25. data/lib/chewy/index/import/bulk_request.rb +1 -1
  26. data/lib/chewy/index/import/routine.rb +3 -3
  27. data/lib/chewy/index/import.rb +45 -31
  28. data/lib/chewy/index/mapping.rb +2 -2
  29. data/lib/chewy/index/observe/active_record_methods.rb +87 -0
  30. data/lib/chewy/index/observe/callback.rb +34 -0
  31. data/lib/chewy/index/observe.rb +3 -58
  32. data/lib/chewy/index/syncer.rb +1 -1
  33. data/lib/chewy/index.rb +25 -0
  34. data/lib/chewy/journal.rb +17 -6
  35. data/lib/chewy/log_subscriber.rb +5 -1
  36. data/lib/chewy/minitest/helpers.rb +77 -0
  37. data/lib/chewy/minitest/search_index_receiver.rb +3 -1
  38. data/lib/chewy/rake_helper.rb +92 -11
  39. data/lib/chewy/rspec/build_query.rb +12 -0
  40. data/lib/chewy/rspec/helpers.rb +55 -0
  41. data/lib/chewy/rspec/update_index.rb +14 -7
  42. data/lib/chewy/rspec.rb +2 -0
  43. data/lib/chewy/runtime/version.rb +1 -1
  44. data/lib/chewy/runtime.rb +1 -1
  45. data/lib/chewy/search/parameters/collapse.rb +16 -0
  46. data/lib/chewy/search/parameters/ignore_unavailable.rb +27 -0
  47. data/lib/chewy/search/parameters/indices.rb +1 -1
  48. data/lib/chewy/search/parameters/knn.rb +16 -0
  49. data/lib/chewy/search/parameters/order.rb +6 -19
  50. data/lib/chewy/search/parameters/storage.rb +1 -1
  51. data/lib/chewy/search/parameters/track_total_hits.rb +16 -0
  52. data/lib/chewy/search/parameters.rb +4 -4
  53. data/lib/chewy/search/request.rb +74 -16
  54. data/lib/chewy/search/scoping.rb +1 -1
  55. data/lib/chewy/search.rb +5 -2
  56. data/lib/chewy/stash.rb +3 -3
  57. data/lib/chewy/strategy/active_job.rb +1 -1
  58. data/lib/chewy/strategy/atomic_no_refresh.rb +18 -0
  59. data/lib/chewy/strategy/base.rb +10 -0
  60. data/lib/chewy/strategy/delayed_sidekiq/scheduler.rb +168 -0
  61. data/lib/chewy/strategy/delayed_sidekiq/worker.rb +76 -0
  62. data/lib/chewy/strategy/delayed_sidekiq.rb +30 -0
  63. data/lib/chewy/strategy/lazy_sidekiq.rb +64 -0
  64. data/lib/chewy/strategy/sidekiq.rb +1 -1
  65. data/lib/chewy/strategy.rb +3 -0
  66. data/lib/chewy/version.rb +1 -1
  67. data/lib/chewy.rb +21 -14
  68. data/lib/tasks/chewy.rake +18 -2
  69. data/migration_guide.md +1 -1
  70. data/spec/chewy/config_spec.rb +2 -2
  71. data/spec/chewy/elastic_client_spec.rb +26 -0
  72. data/spec/chewy/fields/base_spec.rb +39 -18
  73. data/spec/chewy/index/actions_spec.rb +10 -10
  74. data/spec/chewy/index/adapter/active_record_spec.rb +88 -0
  75. data/spec/chewy/index/import/bulk_builder_spec.rb +309 -1
  76. data/spec/chewy/index/import/routine_spec.rb +5 -5
  77. data/spec/chewy/index/import_spec.rb +48 -26
  78. data/spec/chewy/index/observe/active_record_methods_spec.rb +68 -0
  79. data/spec/chewy/index/observe/callback_spec.rb +139 -0
  80. data/spec/chewy/index/observe_spec.rb +27 -0
  81. data/spec/chewy/journal_spec.rb +13 -49
  82. data/spec/chewy/minitest/helpers_spec.rb +111 -1
  83. data/spec/chewy/minitest/search_index_receiver_spec.rb +6 -4
  84. data/spec/chewy/rake_helper_spec.rb +170 -0
  85. data/spec/chewy/rspec/build_query_spec.rb +34 -0
  86. data/spec/chewy/rspec/helpers_spec.rb +61 -0
  87. data/spec/chewy/search/pagination/kaminari_examples.rb +1 -1
  88. data/spec/chewy/search/pagination/kaminari_spec.rb +1 -1
  89. data/spec/chewy/search/parameters/collapse_spec.rb +5 -0
  90. data/spec/chewy/search/parameters/ignore_unavailable_spec.rb +67 -0
  91. data/spec/chewy/search/parameters/knn_spec.rb +5 -0
  92. data/spec/chewy/search/parameters/order_spec.rb +18 -11
  93. data/spec/chewy/search/parameters/track_total_hits_spec.rb +5 -0
  94. data/spec/chewy/search/parameters_spec.rb +6 -1
  95. data/spec/chewy/search/request_spec.rb +58 -9
  96. data/spec/chewy/search_spec.rb +9 -0
  97. data/spec/chewy/strategy/active_job_spec.rb +8 -8
  98. data/spec/chewy/strategy/atomic_no_refresh_spec.rb +60 -0
  99. data/spec/chewy/strategy/delayed_sidekiq_spec.rb +208 -0
  100. data/spec/chewy/strategy/lazy_sidekiq_spec.rb +214 -0
  101. data/spec/chewy/strategy/sidekiq_spec.rb +4 -4
  102. data/spec/chewy_spec.rb +10 -7
  103. data/spec/spec_helper.rb +1 -2
  104. data/spec/support/active_record.rb +8 -1
  105. metadata +45 -264
  106. data/lib/chewy/backports/deep_dup.rb +0 -46
  107. data/lib/chewy/backports/duplicable.rb +0 -91
  108. 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?(::ActiveJob)
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
- ::ActiveJob::Base.logger = Chewy.logger
12
+ ActiveJob::Base.logger = Chewy.logger
13
13
  end
14
14
  before do
15
- ::ActiveJob::Base.queue_adapter = :test
16
- ::ActiveJob::Base.queue_adapter.enqueued_jobs.clear
17
- ::ActiveJob::Base.queue_adapter.performed_jobs.clear
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 = ::ActiveJob::Base.queue_adapter.enqueued_jobs.first
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 = ::ActiveJob::Base.queue_adapter.enqueued_jobs.first
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
- ::ActiveJob::Base.queue_adapter = :inline
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?(::Sidekiq)
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 { ::Sidekiq::Worker.clear_all }
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(::Sidekiq::Client).to receive(:push).with(hash_including('queue' => 'low')).and_call_original
34
- ::Sidekiq::Testing.inline! do
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) { Thread.current[:chewy_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
- Thread.current[:chewy_client] = nil
60
- allow(Chewy).to receive_messages(configuration: {transport_options: {proc: faraday_block}})
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
- allow(::Elasticsearch::Client).to receive(:new).with(expected_client_config) do |*_args, &passed_block|
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
- its(:client) { is_expected.to eq(mock_client) }
73
+ expect(Chewy.client).to be_a(Chewy::ElasticClient)
74
+ end
72
75
 
73
- after { Thread.current[:chewy_client] = initial_client }
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?(::Kaminari::Hooks)
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: 'file::memory:?cache=shared', pool: 1)
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