chewy 6.0.0 → 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.
Files changed (188) hide show
  1. checksums.yaml +4 -4
  2. data/.github/CODEOWNERS +1 -0
  3. data/.github/ISSUE_TEMPLATE/bug_report.md +39 -0
  4. data/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  5. data/.github/PULL_REQUEST_TEMPLATE.md +16 -0
  6. data/.github/dependabot.yml +42 -0
  7. data/.github/workflows/ruby.yml +60 -0
  8. data/.rubocop.yml +16 -8
  9. data/.rubocop_todo.yml +110 -22
  10. data/CHANGELOG.md +396 -105
  11. data/CODE_OF_CONDUCT.md +14 -0
  12. data/CONTRIBUTING.md +63 -0
  13. data/Gemfile +4 -10
  14. data/Guardfile +3 -1
  15. data/README.md +497 -275
  16. data/chewy.gemspec +5 -20
  17. data/gemfiles/base.gemfile +12 -0
  18. data/gemfiles/rails.6.1.activerecord.gemfile +10 -15
  19. data/gemfiles/rails.7.0.activerecord.gemfile +14 -0
  20. data/gemfiles/rails.7.1.activerecord.gemfile +14 -0
  21. data/lib/chewy/config.rb +60 -52
  22. data/lib/chewy/elastic_client.rb +31 -0
  23. data/lib/chewy/errors.rb +7 -10
  24. data/lib/chewy/fields/base.rb +79 -13
  25. data/lib/chewy/fields/root.rb +4 -14
  26. data/lib/chewy/index/actions.rb +54 -37
  27. data/lib/chewy/{type → index}/adapter/active_record.rb +30 -6
  28. data/lib/chewy/{type → index}/adapter/base.rb +2 -3
  29. data/lib/chewy/{type → index}/adapter/object.rb +27 -31
  30. data/lib/chewy/{type → index}/adapter/orm.rb +17 -18
  31. data/lib/chewy/index/aliases.rb +14 -5
  32. data/lib/chewy/index/crutch.rb +40 -0
  33. data/lib/chewy/index/import/bulk_builder.rb +311 -0
  34. data/lib/chewy/{type → index}/import/bulk_request.rb +6 -7
  35. data/lib/chewy/{type → index}/import/journal_builder.rb +11 -12
  36. data/lib/chewy/{type → index}/import/routine.rb +18 -17
  37. data/lib/chewy/{type → index}/import.rb +76 -32
  38. data/lib/chewy/{type → index}/mapping.rb +29 -34
  39. data/lib/chewy/index/observe/active_record_methods.rb +87 -0
  40. data/lib/chewy/index/observe/callback.rb +34 -0
  41. data/lib/chewy/index/observe.rb +17 -0
  42. data/lib/chewy/index/specification.rb +1 -0
  43. data/lib/chewy/{type → index}/syncer.rb +59 -59
  44. data/lib/chewy/{type → index}/witchcraft.rb +11 -7
  45. data/lib/chewy/{type → index}/wrapper.rb +2 -2
  46. data/lib/chewy/index.rb +67 -94
  47. data/lib/chewy/journal.rb +25 -14
  48. data/lib/chewy/log_subscriber.rb +5 -1
  49. data/lib/chewy/minitest/helpers.rb +86 -13
  50. data/lib/chewy/minitest/search_index_receiver.rb +24 -26
  51. data/lib/chewy/railtie.rb +6 -20
  52. data/lib/chewy/rake_helper.rb +169 -113
  53. data/lib/chewy/rspec/build_query.rb +12 -0
  54. data/lib/chewy/rspec/helpers.rb +55 -0
  55. data/lib/chewy/rspec/update_index.rb +55 -44
  56. data/lib/chewy/rspec.rb +2 -0
  57. data/lib/chewy/runtime/version.rb +1 -1
  58. data/lib/chewy/runtime.rb +1 -1
  59. data/lib/chewy/search/loader.rb +19 -41
  60. data/lib/chewy/search/parameters/collapse.rb +16 -0
  61. data/lib/chewy/search/parameters/concerns/query_storage.rb +2 -2
  62. data/lib/chewy/search/parameters/ignore_unavailable.rb +27 -0
  63. data/lib/chewy/search/parameters/indices.rb +13 -58
  64. data/lib/chewy/search/parameters/knn.rb +16 -0
  65. data/lib/chewy/search/parameters/order.rb +6 -19
  66. data/lib/chewy/search/parameters/source.rb +5 -1
  67. data/lib/chewy/search/parameters/storage.rb +1 -1
  68. data/lib/chewy/search/parameters/track_total_hits.rb +16 -0
  69. data/lib/chewy/search/parameters.rb +6 -4
  70. data/lib/chewy/search/query_proxy.rb +9 -2
  71. data/lib/chewy/search/request.rb +169 -134
  72. data/lib/chewy/search/response.rb +5 -5
  73. data/lib/chewy/search/scoping.rb +7 -8
  74. data/lib/chewy/search/scrolling.rb +13 -13
  75. data/lib/chewy/search.rb +9 -19
  76. data/lib/chewy/stash.rb +19 -30
  77. data/lib/chewy/strategy/active_job.rb +1 -1
  78. data/lib/chewy/strategy/atomic_no_refresh.rb +18 -0
  79. data/lib/chewy/strategy/base.rb +10 -0
  80. data/lib/chewy/strategy/delayed_sidekiq/scheduler.rb +168 -0
  81. data/lib/chewy/strategy/delayed_sidekiq/worker.rb +76 -0
  82. data/lib/chewy/strategy/delayed_sidekiq.rb +30 -0
  83. data/lib/chewy/strategy/lazy_sidekiq.rb +64 -0
  84. data/lib/chewy/strategy/sidekiq.rb +2 -1
  85. data/lib/chewy/strategy.rb +6 -19
  86. data/lib/chewy/version.rb +1 -1
  87. data/lib/chewy.rb +39 -86
  88. data/lib/generators/chewy/install_generator.rb +1 -1
  89. data/lib/tasks/chewy.rake +36 -32
  90. data/migration_guide.md +46 -8
  91. data/spec/chewy/config_spec.rb +16 -41
  92. data/spec/chewy/elastic_client_spec.rb +26 -0
  93. data/spec/chewy/fields/base_spec.rb +432 -147
  94. data/spec/chewy/fields/root_spec.rb +20 -28
  95. data/spec/chewy/fields/time_fields_spec.rb +5 -5
  96. data/spec/chewy/index/actions_spec.rb +368 -59
  97. data/spec/chewy/{type → index}/adapter/active_record_spec.rb +156 -40
  98. data/spec/chewy/{type → index}/adapter/object_spec.rb +21 -6
  99. data/spec/chewy/index/aliases_spec.rb +3 -3
  100. data/spec/chewy/index/import/bulk_builder_spec.rb +494 -0
  101. data/spec/chewy/{type → index}/import/bulk_request_spec.rb +5 -12
  102. data/spec/chewy/{type → index}/import/journal_builder_spec.rb +9 -19
  103. data/spec/chewy/{type → index}/import/routine_spec.rb +19 -19
  104. data/spec/chewy/{type → index}/import_spec.rb +164 -98
  105. data/spec/chewy/index/mapping_spec.rb +135 -0
  106. data/spec/chewy/index/observe/active_record_methods_spec.rb +68 -0
  107. data/spec/chewy/index/observe/callback_spec.rb +139 -0
  108. data/spec/chewy/index/observe_spec.rb +143 -0
  109. data/spec/chewy/index/settings_spec.rb +3 -1
  110. data/spec/chewy/index/specification_spec.rb +20 -30
  111. data/spec/chewy/{type → index}/syncer_spec.rb +14 -19
  112. data/spec/chewy/{type → index}/witchcraft_spec.rb +20 -22
  113. data/spec/chewy/index/wrapper_spec.rb +100 -0
  114. data/spec/chewy/index_spec.rb +60 -105
  115. data/spec/chewy/journal_spec.rb +25 -74
  116. data/spec/chewy/minitest/helpers_spec.rb +123 -15
  117. data/spec/chewy/minitest/search_index_receiver_spec.rb +28 -30
  118. data/spec/chewy/multi_search_spec.rb +4 -5
  119. data/spec/chewy/rake_helper_spec.rb +315 -55
  120. data/spec/chewy/rspec/build_query_spec.rb +34 -0
  121. data/spec/chewy/rspec/helpers_spec.rb +61 -0
  122. data/spec/chewy/rspec/update_index_spec.rb +74 -71
  123. data/spec/chewy/runtime_spec.rb +2 -2
  124. data/spec/chewy/search/loader_spec.rb +19 -53
  125. data/spec/chewy/search/pagination/kaminari_examples.rb +4 -6
  126. data/spec/chewy/search/pagination/kaminari_spec.rb +2 -2
  127. data/spec/chewy/search/parameters/collapse_spec.rb +5 -0
  128. data/spec/chewy/search/parameters/ignore_unavailable_spec.rb +67 -0
  129. data/spec/chewy/search/parameters/indices_spec.rb +26 -117
  130. data/spec/chewy/search/parameters/knn_spec.rb +5 -0
  131. data/spec/chewy/search/parameters/order_spec.rb +18 -11
  132. data/spec/chewy/search/parameters/query_storage_examples.rb +67 -21
  133. data/spec/chewy/search/parameters/search_after_spec.rb +4 -1
  134. data/spec/chewy/search/parameters/source_spec.rb +8 -2
  135. data/spec/chewy/search/parameters/track_total_hits_spec.rb +5 -0
  136. data/spec/chewy/search/parameters_spec.rb +18 -4
  137. data/spec/chewy/search/query_proxy_spec.rb +68 -17
  138. data/spec/chewy/search/request_spec.rb +292 -110
  139. data/spec/chewy/search/response_spec.rb +12 -12
  140. data/spec/chewy/search/scrolling_spec.rb +10 -17
  141. data/spec/chewy/search_spec.rb +40 -34
  142. data/spec/chewy/stash_spec.rb +9 -21
  143. data/spec/chewy/strategy/active_job_spec.rb +16 -16
  144. data/spec/chewy/strategy/atomic_no_refresh_spec.rb +60 -0
  145. data/spec/chewy/strategy/atomic_spec.rb +9 -10
  146. data/spec/chewy/strategy/delayed_sidekiq_spec.rb +208 -0
  147. data/spec/chewy/strategy/lazy_sidekiq_spec.rb +214 -0
  148. data/spec/chewy/strategy/sidekiq_spec.rb +12 -12
  149. data/spec/chewy/strategy_spec.rb +19 -15
  150. data/spec/chewy_spec.rb +24 -107
  151. data/spec/spec_helper.rb +3 -22
  152. data/spec/support/active_record.rb +25 -7
  153. metadata +78 -339
  154. data/.circleci/config.yml +0 -240
  155. data/Appraisals +0 -81
  156. data/gemfiles/rails.5.2.activerecord.gemfile +0 -17
  157. data/gemfiles/rails.5.2.mongoid.6.4.gemfile +0 -17
  158. data/gemfiles/rails.6.0.activerecord.gemfile +0 -17
  159. data/gemfiles/sequel.4.45.gemfile +0 -11
  160. data/lib/chewy/backports/deep_dup.rb +0 -46
  161. data/lib/chewy/backports/duplicable.rb +0 -91
  162. data/lib/chewy/search/pagination/will_paginate.rb +0 -43
  163. data/lib/chewy/search/parameters/types.rb +0 -20
  164. data/lib/chewy/strategy/resque.rb +0 -27
  165. data/lib/chewy/strategy/shoryuken.rb +0 -40
  166. data/lib/chewy/type/actions.rb +0 -43
  167. data/lib/chewy/type/adapter/mongoid.rb +0 -67
  168. data/lib/chewy/type/adapter/sequel.rb +0 -93
  169. data/lib/chewy/type/crutch.rb +0 -32
  170. data/lib/chewy/type/import/bulk_builder.rb +0 -122
  171. data/lib/chewy/type/observe.rb +0 -82
  172. data/lib/chewy/type.rb +0 -120
  173. data/lib/sequel/plugins/chewy_observe.rb +0 -63
  174. data/spec/chewy/search/pagination/will_paginate_examples.rb +0 -63
  175. data/spec/chewy/search/pagination/will_paginate_spec.rb +0 -23
  176. data/spec/chewy/search/parameters/types_spec.rb +0 -5
  177. data/spec/chewy/strategy/resque_spec.rb +0 -46
  178. data/spec/chewy/strategy/shoryuken_spec.rb +0 -70
  179. data/spec/chewy/type/actions_spec.rb +0 -50
  180. data/spec/chewy/type/adapter/mongoid_spec.rb +0 -372
  181. data/spec/chewy/type/adapter/sequel_spec.rb +0 -472
  182. data/spec/chewy/type/import/bulk_builder_spec.rb +0 -194
  183. data/spec/chewy/type/mapping_spec.rb +0 -175
  184. data/spec/chewy/type/observe_spec.rb +0 -137
  185. data/spec/chewy/type/wrapper_spec.rb +0 -100
  186. data/spec/chewy/type_spec.rb +0 -55
  187. data/spec/support/mongoid.rb +0 -93
  188. data/spec/support/sequel.rb +0 -80
@@ -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,14 +10,14 @@ 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
- update_index('cities#city') { self }
16
+ update_index('cities') { self }
17
17
  end
18
18
 
19
19
  stub_index(:cities) do
20
- define_type City
20
+ index_scope City
21
21
  end
22
22
  end
23
23
 
@@ -26,27 +26,27 @@ if defined?(::Sidekiq)
26
26
 
27
27
  specify do
28
28
  expect { [city, other_city].map(&:save!) }
29
- .not_to update_index(CitiesIndex::City, strategy: :sidekiq)
29
+ .not_to update_index(CitiesIndex, strategy: :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
- .to update_index(CitiesIndex::City, strategy: :sidekiq)
36
+ .to update_index(CitiesIndex, strategy: :sidekiq)
37
37
  .and_reindex(city, other_city).only
38
38
  end
39
39
  end
40
40
 
41
41
  specify do
42
- expect(CitiesIndex::City).to receive(:import!).with([city.id, other_city.id], suffix: '201601')
43
- Chewy::Strategy::Sidekiq::Worker.new.perform('CitiesIndex::City', [city.id, other_city.id], suffix: '201601')
42
+ expect(CitiesIndex).to receive(:import!).with([city.id, other_city.id], suffix: '201601')
43
+ Chewy::Strategy::Sidekiq::Worker.new.perform('CitiesIndex', [city.id, other_city.id], suffix: '201601')
44
44
  end
45
45
 
46
46
  specify do
47
47
  allow(Chewy).to receive(:disable_refresh_async).and_return(true)
48
- expect(CitiesIndex::City).to receive(:import!).with([city.id, other_city.id], suffix: '201601', refresh: false)
49
- Chewy::Strategy::Sidekiq::Worker.new.perform('CitiesIndex::City', [city.id, other_city.id], suffix: '201601')
48
+ expect(CitiesIndex).to receive(:import!).with([city.id, other_city.id], suffix: '201601', refresh: false)
49
+ Chewy::Strategy::Sidekiq::Worker.new.perform('CitiesIndex', [city.id, other_city.id], suffix: '201601')
50
50
  end
51
51
  end
52
52
  end
@@ -14,7 +14,10 @@ describe Chewy::Strategy do
14
14
  end
15
15
 
16
16
  describe '#push' do
17
- specify { expect { strategy.push(:unexistant) }.to raise_error(RuntimeError).with_message("Can't find update strategy `unexistant`") }
17
+ specify do
18
+ expect { strategy.push(:unexistant) }
19
+ .to raise_error(RuntimeError).with_message("Can't find update strategy `unexistant`")
20
+ end
18
21
 
19
22
  specify do
20
23
  expect { strategy.push(:atomic) }
@@ -35,7 +38,10 @@ describe Chewy::Strategy do
35
38
  end
36
39
 
37
40
  describe '#wrap' do
38
- specify { expect { strategy.wrap(:unexistant) {} }.to raise_error(RuntimeError).with_message("Can't find update strategy `unexistant`") }
41
+ specify do
42
+ expect { strategy.wrap(:unexistant) {} }
43
+ .to raise_error(RuntimeError).with_message("Can't find update strategy `unexistant`")
44
+ end
39
45
 
40
46
  specify do
41
47
  expect do
@@ -49,11 +55,11 @@ describe Chewy::Strategy do
49
55
  context 'nesting', :orm do
50
56
  before do
51
57
  stub_model(:city) do
52
- update_index('cities#city') { self }
58
+ update_index('cities') { self }
53
59
  end
54
60
 
55
61
  stub_index(:cities) do
56
- define_type City
62
+ index_scope City
57
63
  end
58
64
  end
59
65
 
@@ -64,12 +70,12 @@ describe Chewy::Strategy do
64
70
  around { |example| Chewy.strategy(:bypass) { example.run } }
65
71
 
66
72
  specify do
67
- expect(CitiesIndex::City).not_to receive(:import!)
73
+ expect(CitiesIndex).not_to receive(:import!)
68
74
  [city, other_city].map(&:save!)
69
75
  end
70
76
 
71
77
  specify do
72
- expect(CitiesIndex::City).to receive(:import!).with([city.id, other_city.id]).once
78
+ expect(CitiesIndex).to receive(:import!).with([city.id, other_city.id]).once
73
79
  Chewy.strategy(:atomic) { [city, other_city].map(&:save!) }
74
80
  end
75
81
  end
@@ -78,41 +84,39 @@ describe Chewy::Strategy do
78
84
  around { |example| Chewy.strategy(:urgent) { example.run } }
79
85
 
80
86
  specify do
81
- expect(CitiesIndex::City).to receive(:import!).at_least(2).times
87
+ expect(CitiesIndex).to receive(:import!).at_least(2).times
82
88
  [city, other_city].map(&:save!)
83
89
  end
84
90
 
85
91
  specify do
86
- expect(CitiesIndex::City).to receive(:import!).with([city.id, other_city.id]).once
92
+ expect(CitiesIndex).to receive(:import!).with([city.id, other_city.id]).once
87
93
  Chewy.strategy(:atomic) { [city, other_city].map(&:save!) }
88
94
  end
89
95
 
90
96
  context 'hash passed to urgent' do
91
97
  before do
92
- stub_index(:cities) do
93
- define_type :city
94
- end
98
+ stub_index(:cities)
95
99
 
96
100
  stub_model(:city) do
97
- update_index('cities#city') { {name: name} }
101
+ update_index('cities') { {name: name} }
98
102
  end
99
103
  end
100
104
 
101
105
  specify do
102
106
  [city, other_city].map(&:save!)
103
- expect(CitiesIndex::City.total_count).to eq(4)
107
+ expect(CitiesIndex.total_count).to eq(4)
104
108
  end
105
109
 
106
110
  context do
107
111
  before do
108
112
  stub_model(:city) do
109
- update_index('cities#city') { {id: id.to_s, name: name} }
113
+ update_index('cities') { {id: id.to_s, name: name} }
110
114
  end
111
115
  end
112
116
 
113
117
  specify do
114
118
  [city, other_city].map(&:save!)
115
- expect(CitiesIndex::City.total_count).to eq(2)
119
+ expect(CitiesIndex.total_count).to eq(2)
116
120
  end
117
121
  end
118
122
  end