chewy 8.0.0 → 8.1.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 (144) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +42 -0
  3. data/README.md +30 -16
  4. data/lib/chewy/errors.rb +3 -0
  5. data/lib/chewy/fields/root.rb +3 -3
  6. data/lib/chewy/index/crutch.rb +12 -2
  7. data/lib/chewy/index/import/bulk_builder.rb +4 -3
  8. data/lib/chewy/index/import/routine.rb +2 -1
  9. data/lib/chewy/index/import.rb +4 -4
  10. data/lib/chewy/index/witchcraft.rb +24 -8
  11. data/lib/chewy/multi_search.rb +1 -1
  12. data/lib/chewy/search/parameters/runtime_mappings.rb +14 -0
  13. data/lib/chewy/search/request.rb +18 -2
  14. data/lib/chewy/search/scrolling.rb +14 -6
  15. data/lib/chewy/stash.rb +10 -6
  16. data/lib/chewy/version.rb +1 -1
  17. metadata +5 -131
  18. data/.github/CODEOWNERS +0 -1
  19. data/.github/ISSUE_TEMPLATE/bug_report.md +0 -39
  20. data/.github/ISSUE_TEMPLATE/feature_request.md +0 -20
  21. data/.github/PULL_REQUEST_TEMPLATE.md +0 -16
  22. data/.github/dependabot.yml +0 -42
  23. data/.github/workflows/ruby.yml +0 -61
  24. data/.gitignore +0 -22
  25. data/.rspec +0 -2
  26. data/.rubocop.yml +0 -64
  27. data/.rubocop_todo.yml +0 -225
  28. data/.yardopts +0 -5
  29. data/CODE_OF_CONDUCT.md +0 -14
  30. data/CONTRIBUTING.md +0 -63
  31. data/Gemfile +0 -22
  32. data/Guardfile +0 -25
  33. data/Rakefile +0 -17
  34. data/chewy.gemspec +0 -24
  35. data/docker-compose.yml +0 -14
  36. data/docs/README.md +0 -16
  37. data/docs/configuration.md +0 -440
  38. data/docs/import.md +0 -122
  39. data/docs/indexing.md +0 -329
  40. data/docs/querying.md +0 -72
  41. data/docs/rake_tasks.md +0 -108
  42. data/docs/testing.md +0 -41
  43. data/docs/troubleshooting.md +0 -101
  44. data/filters +0 -78
  45. data/gemfiles/base.gemfile +0 -12
  46. data/gemfiles/rails.7.2.activerecord.gemfile +0 -14
  47. data/gemfiles/rails.8.0.activerecord.gemfile +0 -14
  48. data/migration_guide.md +0 -56
  49. data/spec/chewy/config_spec.rb +0 -110
  50. data/spec/chewy/elastic_client_spec.rb +0 -26
  51. data/spec/chewy/fields/base_spec.rb +0 -700
  52. data/spec/chewy/fields/root_spec.rb +0 -142
  53. data/spec/chewy/fields/time_fields_spec.rb +0 -28
  54. data/spec/chewy/index/actions_spec.rb +0 -851
  55. data/spec/chewy/index/adapter/active_record_spec.rb +0 -663
  56. data/spec/chewy/index/adapter/object_spec.rb +0 -243
  57. data/spec/chewy/index/aliases_spec.rb +0 -49
  58. data/spec/chewy/index/import/bulk_builder_spec.rb +0 -494
  59. data/spec/chewy/index/import/bulk_request_spec.rb +0 -95
  60. data/spec/chewy/index/import/journal_builder_spec.rb +0 -87
  61. data/spec/chewy/index/import/routine_spec.rb +0 -110
  62. data/spec/chewy/index/import_spec.rb +0 -615
  63. data/spec/chewy/index/mapping_spec.rb +0 -135
  64. data/spec/chewy/index/observe/active_record_methods_spec.rb +0 -68
  65. data/spec/chewy/index/observe/callback_spec.rb +0 -139
  66. data/spec/chewy/index/observe_spec.rb +0 -143
  67. data/spec/chewy/index/settings_spec.rb +0 -136
  68. data/spec/chewy/index/specification_spec.rb +0 -156
  69. data/spec/chewy/index/syncer_spec.rb +0 -118
  70. data/spec/chewy/index/witchcraft_spec.rb +0 -245
  71. data/spec/chewy/index/wrapper_spec.rb +0 -100
  72. data/spec/chewy/index_spec.rb +0 -269
  73. data/spec/chewy/journal_spec.rb +0 -223
  74. data/spec/chewy/minitest/helpers_spec.rb +0 -194
  75. data/spec/chewy/minitest/search_index_receiver_spec.rb +0 -120
  76. data/spec/chewy/multi_search_spec.rb +0 -84
  77. data/spec/chewy/rake_helper_spec.rb +0 -656
  78. data/spec/chewy/repository_spec.rb +0 -50
  79. data/spec/chewy/rspec/build_query_spec.rb +0 -34
  80. data/spec/chewy/rspec/helpers_spec.rb +0 -61
  81. data/spec/chewy/rspec/update_index_spec.rb +0 -313
  82. data/spec/chewy/runtime/version_spec.rb +0 -48
  83. data/spec/chewy/runtime_spec.rb +0 -9
  84. data/spec/chewy/search/loader_spec.rb +0 -83
  85. data/spec/chewy/search/pagination/kaminari_examples.rb +0 -69
  86. data/spec/chewy/search/pagination/kaminari_spec.rb +0 -21
  87. data/spec/chewy/search/parameters/aggs_spec.rb +0 -5
  88. data/spec/chewy/search/parameters/bool_storage_examples.rb +0 -53
  89. data/spec/chewy/search/parameters/collapse_spec.rb +0 -5
  90. data/spec/chewy/search/parameters/docvalue_fields_spec.rb +0 -5
  91. data/spec/chewy/search/parameters/explain_spec.rb +0 -5
  92. data/spec/chewy/search/parameters/filter_spec.rb +0 -5
  93. data/spec/chewy/search/parameters/hash_storage_examples.rb +0 -59
  94. data/spec/chewy/search/parameters/highlight_spec.rb +0 -5
  95. data/spec/chewy/search/parameters/ignore_unavailable_spec.rb +0 -67
  96. data/spec/chewy/search/parameters/indices_spec.rb +0 -99
  97. data/spec/chewy/search/parameters/integer_storage_examples.rb +0 -32
  98. data/spec/chewy/search/parameters/knn_spec.rb +0 -5
  99. data/spec/chewy/search/parameters/limit_spec.rb +0 -5
  100. data/spec/chewy/search/parameters/load_spec.rb +0 -60
  101. data/spec/chewy/search/parameters/min_score_spec.rb +0 -32
  102. data/spec/chewy/search/parameters/none_spec.rb +0 -5
  103. data/spec/chewy/search/parameters/offset_spec.rb +0 -5
  104. data/spec/chewy/search/parameters/order_spec.rb +0 -72
  105. data/spec/chewy/search/parameters/post_filter_spec.rb +0 -5
  106. data/spec/chewy/search/parameters/preference_spec.rb +0 -5
  107. data/spec/chewy/search/parameters/profile_spec.rb +0 -5
  108. data/spec/chewy/search/parameters/query_spec.rb +0 -5
  109. data/spec/chewy/search/parameters/query_storage_examples.rb +0 -434
  110. data/spec/chewy/search/parameters/request_cache_spec.rb +0 -67
  111. data/spec/chewy/search/parameters/rescore_spec.rb +0 -62
  112. data/spec/chewy/search/parameters/script_fields_spec.rb +0 -5
  113. data/spec/chewy/search/parameters/search_after_spec.rb +0 -35
  114. data/spec/chewy/search/parameters/search_type_spec.rb +0 -5
  115. data/spec/chewy/search/parameters/source_spec.rb +0 -162
  116. data/spec/chewy/search/parameters/storage_spec.rb +0 -60
  117. data/spec/chewy/search/parameters/stored_fields_spec.rb +0 -126
  118. data/spec/chewy/search/parameters/string_array_storage_examples.rb +0 -63
  119. data/spec/chewy/search/parameters/string_storage_examples.rb +0 -32
  120. data/spec/chewy/search/parameters/suggest_spec.rb +0 -5
  121. data/spec/chewy/search/parameters/terminate_after_spec.rb +0 -5
  122. data/spec/chewy/search/parameters/timeout_spec.rb +0 -5
  123. data/spec/chewy/search/parameters/track_scores_spec.rb +0 -5
  124. data/spec/chewy/search/parameters/track_total_hits_spec.rb +0 -5
  125. data/spec/chewy/search/parameters/version_spec.rb +0 -5
  126. data/spec/chewy/search/parameters_spec.rb +0 -161
  127. data/spec/chewy/search/query_proxy_spec.rb +0 -95
  128. data/spec/chewy/search/request_spec.rb +0 -886
  129. data/spec/chewy/search/response_spec.rb +0 -180
  130. data/spec/chewy/search/scrolling_spec.rb +0 -171
  131. data/spec/chewy/search_spec.rb +0 -127
  132. data/spec/chewy/stash_spec.rb +0 -85
  133. data/spec/chewy/strategy/active_job_spec.rb +0 -73
  134. data/spec/chewy/strategy/atomic_no_refresh_spec.rb +0 -60
  135. data/spec/chewy/strategy/atomic_spec.rb +0 -61
  136. data/spec/chewy/strategy/delayed_sidekiq_spec.rb +0 -225
  137. data/spec/chewy/strategy/lazy_sidekiq_spec.rb +0 -214
  138. data/spec/chewy/strategy/sidekiq_spec.rb +0 -52
  139. data/spec/chewy/strategy_spec.rb +0 -125
  140. data/spec/chewy_spec.rb +0 -100
  141. data/spec/spec_helper.rb +0 -69
  142. data/spec/support/active_record.rb +0 -124
  143. data/spec/support/class_helpers.rb +0 -16
  144. data/spec/support/fail_helpers.rb +0 -13
@@ -1,663 +0,0 @@
1
- require 'spec_helper'
2
-
3
- RawCity = Struct.new(:id) do
4
- def rating
5
- id * 10
6
- end
7
- end
8
-
9
- describe Chewy::Index::Adapter::ActiveRecord, :active_record do
10
- before do
11
- stub_model(:city)
12
- stub_model(:country)
13
- City.belongs_to :country
14
- Country.has_many :cities
15
- end
16
-
17
- describe '#name' do
18
- specify { expect(described_class.new(City).name).to eq('City') }
19
- specify { expect(described_class.new(City.order(:id)).name).to eq('City') }
20
- specify { expect(described_class.new(City, name: 'town').name).to eq('Town') }
21
-
22
- context do
23
- before { stub_model('namespace/city') }
24
-
25
- specify { expect(described_class.new(Namespace::City).name).to eq('City') }
26
- specify { expect(described_class.new(Namespace::City.order(:id)).name).to eq('City') }
27
- end
28
- end
29
-
30
- describe '#default_scope' do
31
- specify { expect(described_class.new(City).default_scope).to eq(City.where(nil)) }
32
- specify { expect(described_class.new(City.order(:id)).default_scope).to eq(City.where(nil)) }
33
- specify { expect(described_class.new(City.limit(10)).default_scope).to eq(City.where(nil)) }
34
- specify { expect(described_class.new(City.offset(10)).default_scope).to eq(City.where(nil)) }
35
- specify { expect(described_class.new(City.where(rating: 10)).default_scope).to eq(City.where(rating: 10)) }
36
- end
37
-
38
- describe '.new' do
39
- context 'with logger' do
40
- let(:test_logger) { Logger.new('/dev/null') }
41
- let(:default_scope_behavior) { :warn }
42
-
43
- around do |example|
44
- previous_logger = Chewy.logger
45
- Chewy.logger = test_logger
46
-
47
- previous_default_scope_behavior = Chewy.config.import_scope_cleanup_behavior
48
- Chewy.config.import_scope_cleanup_behavior = default_scope_behavior
49
-
50
- example.run
51
- ensure
52
- Chewy.logger = previous_logger
53
- Chewy.config.import_scope_cleanup_behavior = previous_default_scope_behavior
54
- end
55
-
56
- specify do
57
- expect(test_logger).to receive(:warn)
58
- described_class.new(City.order(:id))
59
- end
60
-
61
- specify do
62
- expect(test_logger).to receive(:warn)
63
- described_class.new(City.offset(10))
64
- end
65
-
66
- specify do
67
- expect(test_logger).to receive(:warn)
68
- described_class.new(City.limit(10))
69
- end
70
-
71
- context 'ignore import scope warning' do
72
- let(:default_scope_behavior) { :ignore }
73
-
74
- specify do
75
- expect(test_logger).not_to receive(:warn)
76
- described_class.new(City.order(:id))
77
- end
78
-
79
- specify do
80
- expect(test_logger).not_to receive(:warn)
81
- described_class.new(City.offset(10))
82
- end
83
-
84
- specify do
85
- expect(test_logger).not_to receive(:warn)
86
- described_class.new(City.limit(10))
87
- end
88
- end
89
-
90
- context 'raise exception on import scope with order/limit/offset' do
91
- let(:default_scope_behavior) { :raise }
92
-
93
- specify { expect { described_class.new(City.order(:id)) }.to raise_error(Chewy::ImportScopeCleanupError) }
94
- specify { expect { described_class.new(City.limit(10)) }.to raise_error(Chewy::ImportScopeCleanupError) }
95
- specify { expect { described_class.new(City.offset(10)) }.to raise_error(Chewy::ImportScopeCleanupError) }
96
- end
97
- end
98
- end
99
-
100
- describe '#type_name' do
101
- specify { expect(described_class.new(City).type_name).to eq('city') }
102
- specify { expect(described_class.new(City.order(:id)).type_name).to eq('city') }
103
- specify { expect(described_class.new(City, name: 'town').type_name).to eq('town') }
104
-
105
- context do
106
- before { stub_model('namespace/city') }
107
-
108
- specify { expect(described_class.new(Namespace::City).type_name).to eq('city') }
109
- specify { expect(described_class.new(Namespace::City.order(:id)).type_name).to eq('city') }
110
- end
111
- end
112
-
113
- describe '#identify' do
114
- subject { described_class.new(City) }
115
-
116
- context do
117
- let!(:cities) { Array.new(3) { City.create! } }
118
-
119
- specify { expect(subject.identify(City.where(nil))).to match_array(cities.map(&:id)) }
120
- specify { expect(subject.identify(cities)).to eq(cities.map(&:id)) }
121
- specify { expect(subject.identify(cities.first)).to eq([cities.first.id]) }
122
- specify { expect(subject.identify(cities.first(2).map(&:id))).to eq(cities.first(2).map(&:id)) }
123
- end
124
-
125
- context 'custom primary_key' do
126
- before { stub_model(:city) { self.primary_key = 'rating' } }
127
- let!(:cities) { Array.new(3) { |i| City.create! { |c| c.rating = i } } }
128
-
129
- specify { expect(subject.identify(City.where(nil))).to match_array([0, 1, 2]) }
130
- specify { expect(subject.identify(cities)).to eq([0, 1, 2]) }
131
- specify { expect(subject.identify(cities.first)).to eq([0]) }
132
- specify { expect(subject.identify(cities.first(2).map(&:id))).to eq([0, 1]) }
133
- end
134
- end
135
-
136
- describe '#import' do
137
- def import(*args)
138
- result = []
139
- subject.import(*args) { |data| result.push data }
140
- result
141
- end
142
-
143
- context do
144
- let!(:cities) { Array.new(3) { City.create! } }
145
- let!(:deleted) { Array.new(4) { City.create!.tap(&:destroy) } }
146
- subject { described_class.new(City) }
147
-
148
- specify { expect(import).to eq([{index: cities}]) }
149
- specify { expect(import(nil)).to eq([]) }
150
-
151
- specify { expect(import(City.order(:id))).to eq([{index: cities}]) }
152
- specify do
153
- expect(import(City.order(:id), batch_size: 2))
154
- .to eq([{index: cities.first(2)}, {index: cities.last(1)}])
155
- end
156
-
157
- specify do
158
- cities
159
- expects_db_queries do
160
- expect(import(cities, direct_import: false)).to eq([{index: cities}])
161
- end
162
- end
163
- specify do
164
- cities
165
- expects_no_query do
166
- expect(import(cities, direct_import: true)).to eq([{index: cities}])
167
- end
168
- end
169
-
170
- specify do
171
- expect(import(cities, batch_size: 2))
172
- .to eq([{index: cities.first(2)}, {index: cities.last(1)}])
173
- end
174
- specify do
175
- expect(import(cities, deleted))
176
- .to eq([{index: cities}, {delete: deleted}])
177
- end
178
- specify do
179
- expect(import(cities, deleted, batch_size: 2)).to eq([
180
- {index: cities.first(2)},
181
- {index: cities.last(1)},
182
- {delete: deleted.first(2)},
183
- {delete: deleted.last(2)}
184
- ])
185
- end
186
-
187
- specify { expect(import(cities.map(&:id))).to eq([{index: cities}]) }
188
- specify { expect(import(deleted.map(&:id))).to eq([{delete: deleted.map(&:id)}]) }
189
- specify do
190
- expect(import(cities.map(&:id), batch_size: 2))
191
- .to eq([{index: cities.first(2)}, {index: cities.last(1)}])
192
- end
193
- specify do
194
- expect(import(cities.map(&:id), deleted.map(&:id)))
195
- .to eq([{index: cities}, {delete: deleted.map(&:id)}])
196
- end
197
- specify do
198
- expect(import(cities.map(&:id), deleted.map(&:id), batch_size: 2)).to eq([
199
- {index: cities.first(2)},
200
- {index: cities.last(1)},
201
- {delete: deleted.first(2).map(&:id)},
202
- {delete: deleted.last(2).map(&:id)}
203
- ])
204
- end
205
-
206
- specify { expect(import(cities.first, nil)).to eq([{index: [cities.first]}]) }
207
- specify { expect(import(cities.first.id, nil)).to eq([{index: [cities.first]}]) }
208
-
209
- context 'raw_import' do
210
- before do
211
- stub_class(:dummy_city) do
212
- def initialize(attributes = {})
213
- @attributes = attributes
214
- end
215
-
216
- def method_missing(name, *args, &block)
217
- if @attributes.key?(name.to_s)
218
- @attributes[name.to_s]
219
- else
220
- super
221
- end
222
- end
223
-
224
- def respond_to_missing?(name, _)
225
- @attributes.key?(name.to_s)
226
- end
227
- end
228
- end
229
- let!(:cities) { Array.new(3) { |i| City.create!(id: i + 1, name: "City#{i + 1}") } }
230
- let(:converter) { ->(hash) { DummyCity.new(hash) } }
231
-
232
- it 'uses the raw import converter to make objects out of raw hashes from the database' do
233
- expect(City).not_to receive(:new)
234
-
235
- expect(import(City.where(nil), raw_import: converter)).to match([{index: match_array([
236
- an_instance_of(DummyCity).and(have_attributes(id: 1, name: 'City1')),
237
- an_instance_of(DummyCity).and(have_attributes(id: 2, name: 'City2')),
238
- an_instance_of(DummyCity).and(have_attributes(id: 3, name: 'City3'))
239
- ])}])
240
- end
241
-
242
- specify do
243
- expect(import([1, 2, 3], raw_import: converter)).to match([{index: match_array([
244
- an_instance_of(DummyCity).and(have_attributes(id: 1, name: 'City1')),
245
- an_instance_of(DummyCity).and(have_attributes(id: 2, name: 'City2')),
246
- an_instance_of(DummyCity).and(have_attributes(id: 3, name: 'City3'))
247
- ])}])
248
- end
249
-
250
- specify do
251
- expect(import(cities, raw_import: converter)).to match([{index: match_array([
252
- an_instance_of(DummyCity).and(have_attributes(id: 1, name: 'City1')),
253
- an_instance_of(DummyCity).and(have_attributes(id: 2, name: 'City2')),
254
- an_instance_of(DummyCity).and(have_attributes(id: 3, name: 'City3'))
255
- ])}])
256
- end
257
- end
258
- end
259
-
260
- context 'additional delete conditions' do
261
- let!(:cities) { Array.new(4) { |i| City.create! rating: i } }
262
- before { cities.last(2).map(&:destroy) }
263
- subject { described_class.new(City) }
264
-
265
- before do
266
- City.class_eval do
267
- def delete_already?
268
- rating.in?([1, 3])
269
- end
270
- end
271
- end
272
- subject { described_class.new(City, delete_if: -> { delete_already? }) }
273
-
274
- specify do
275
- expect(import(City.where(nil))).to eq([
276
- {index: [cities[0]], delete: [cities[1]]}
277
- ])
278
- end
279
- specify do
280
- expect(import(cities)).to eq([
281
- {index: [cities[0]], delete: [cities[1]]},
282
- {delete: cities.last(2)}
283
- ])
284
- end
285
- specify do
286
- expect(import(cities.map(&:id))).to eq([
287
- {index: [cities[0]], delete: [cities[1]]},
288
- {delete: cities.last(2).map(&:id)}
289
- ])
290
- end
291
- end
292
-
293
- context 'custom primary_key' do
294
- before { stub_model(:city) { self.primary_key = 'rating' } }
295
- let!(:cities) { Array.new(3) { |i| City.create! { |c| c.rating = i + 7 } } }
296
- let!(:deleted) { Array.new(3) { |i| City.create! { |c| c.rating = i + 10 }.tap(&:destroy) } }
297
- subject { described_class.new(City) }
298
-
299
- specify { expect(import).to eq([{index: cities}]) }
300
-
301
- specify { expect(import(City.order(:rating))).to eq([{index: cities}]) }
302
- specify do
303
- expect(import(City.order(:rating), batch_size: 2))
304
- .to eq([{index: cities.first(2)}, {index: cities.last(1)}])
305
- end
306
-
307
- specify { expect(import(cities)).to eq([{index: cities}]) }
308
- specify do
309
- expect(import(cities, batch_size: 2))
310
- .to eq([{index: cities.first(2)}, {index: cities.last(1)}])
311
- end
312
- specify do
313
- expect(import(cities, deleted))
314
- .to eq([{index: cities}, {delete: deleted}])
315
- end
316
- specify do
317
- expect(import(cities, deleted, batch_size: 2)).to eq([
318
- {index: cities.first(2)},
319
- {index: cities.last(1)},
320
- {delete: deleted.first(2)},
321
- {delete: deleted.last(1)}
322
- ])
323
- end
324
-
325
- specify { expect(import(cities.map(&:id))).to eq([{index: cities}]) }
326
- specify do
327
- expect(import(cities.map(&:id), batch_size: 2))
328
- .to eq([{index: cities.first(2)}, {index: cities.last(1)}])
329
- end
330
- specify do
331
- expect(import(cities.map(&:id), deleted.map(&:id)))
332
- .to eq([{index: cities}, {delete: deleted.map(&:id)}])
333
- end
334
- specify do
335
- expect(import(cities.map(&:id), deleted.map(&:id), batch_size: 2)).to eq([
336
- {index: cities.first(2)},
337
- {index: cities.last(1)},
338
- {delete: deleted.first(2).map(&:id)},
339
- {delete: deleted.last(1).map(&:id)}
340
- ])
341
- end
342
- end
343
-
344
- context 'default scope' do
345
- let!(:cities) { Array.new(4) { |i| City.create!(rating: i / 3) } }
346
- let!(:deleted) { Array.new(3) { City.create!.tap(&:destroy) } }
347
- subject { described_class.new(City.where(rating: 0)) }
348
-
349
- specify { expect(import).to eq([{index: cities.first(3)}]) }
350
-
351
- specify do
352
- expect(import(City.where('rating < 2')))
353
- .to eq([{index: cities.first(3)}])
354
- end
355
- specify do
356
- expect(import(City.where('rating < 2'), batch_size: 2))
357
- .to eq([{index: cities.first(2)}, {index: [cities[2]]}])
358
- end
359
- specify do
360
- expect(import(City.where('rating < 1')))
361
- .to eq([{index: cities.first(3)}])
362
- end
363
- specify { expect(import(City.where('rating > 1'))).to eq([]) }
364
-
365
- specify do
366
- expect(import(cities.first(2)))
367
- .to eq([{index: cities.first(2)}])
368
- end
369
- specify do
370
- expect(import(cities))
371
- .to eq([{index: cities.first(3)}, {delete: cities.last(1)}])
372
- end
373
- specify do
374
- expect(import(cities, batch_size: 2))
375
- .to eq([{index: cities.first(2)}, {index: [cities[2]]}, {delete: cities.last(1)}])
376
- end
377
- specify do
378
- expect(import(cities, deleted))
379
- .to eq([{index: cities.first(3)}, {delete: cities.last(1) + deleted}])
380
- end
381
- specify do
382
- expect(import(cities, deleted, batch_size: 3)).to eq([
383
- {index: cities.first(3)},
384
- {delete: cities.last(1) + deleted.first(2)},
385
- {delete: deleted.last(1)}
386
- ])
387
- end
388
-
389
- specify do
390
- expect(import(cities.first(2).map(&:id)))
391
- .to eq([{index: cities.first(2)}])
392
- end
393
- specify do
394
- expect(import(cities.map(&:id)))
395
- .to eq([{index: cities.first(3)}, {delete: [cities.last.id]}])
396
- end
397
- specify do
398
- expect(import(cities.map(&:id), batch_size: 2))
399
- .to eq([{index: cities.first(2)}, {index: [cities[2]]}, {delete: [cities.last.id]}])
400
- end
401
- specify do
402
- expect(import(cities.map(&:id), deleted.map(&:id)))
403
- .to eq([{index: cities.first(3)}, {delete: [cities.last.id] + deleted.map(&:id)}])
404
- end
405
- specify do
406
- expect(import(cities.map(&:id), deleted.map(&:id), batch_size: 3)).to eq([
407
- {index: cities.first(3)},
408
- {delete: [cities.last.id] + deleted.first(2).map(&:id)},
409
- {delete: deleted.last(1).map(&:id)}
410
- ])
411
- end
412
- end
413
-
414
- context 'error handling' do
415
- let!(:cities) { Array.new(3) { City.create! } }
416
- let!(:deleted) { Array.new(2) { City.create!.tap(&:destroy) } }
417
- let(:ids) { (cities + deleted).map(&:id) }
418
- subject { described_class.new(City) }
419
-
420
- let(:data_comparer) do
421
- lambda do |id, data|
422
- objects = data[:index] || data[:delete]
423
- !objects.map { |o| o.respond_to?(:id) ? o.id : o }.include?(id)
424
- end
425
- end
426
-
427
- context 'implicit scope' do
428
- specify { expect(subject.import { |_data| true }).to eq(true) }
429
- specify { expect(subject.import { |_data| false }).to eq(false) }
430
- specify { expect(subject.import(batch_size: 1, &data_comparer.curry[cities[0].id])).to eq(false) }
431
- specify { expect(subject.import(batch_size: 1, &data_comparer.curry[cities[1].id])).to eq(false) }
432
- specify { expect(subject.import(batch_size: 1, &data_comparer.curry[cities[2].id])).to eq(false) }
433
- specify { expect(subject.import(batch_size: 1, &data_comparer.curry[deleted[0].id])).to eq(true) }
434
- specify { expect(subject.import(batch_size: 1, &data_comparer.curry[deleted[1].id])).to eq(true) }
435
- end
436
-
437
- context 'explicit scope' do
438
- let(:scope) { City.where(id: ids) }
439
-
440
- specify { expect(subject.import(scope) { |_data| true }).to eq(true) }
441
- specify { expect(subject.import(scope) { |_data| false }).to eq(false) }
442
- specify { expect(subject.import(scope, batch_size: 1, &data_comparer.curry[cities[0].id])).to eq(false) }
443
- specify { expect(subject.import(scope, batch_size: 1, &data_comparer.curry[cities[1].id])).to eq(false) }
444
- specify { expect(subject.import(scope, batch_size: 1, &data_comparer.curry[cities[2].id])).to eq(false) }
445
- specify { expect(subject.import(scope, batch_size: 1, &data_comparer.curry[deleted[0].id])).to eq(true) }
446
- specify { expect(subject.import(scope, batch_size: 1, &data_comparer.curry[deleted[1].id])).to eq(true) }
447
- end
448
-
449
- context 'objects' do
450
- specify { expect(subject.import(cities + deleted) { |_data| true }).to eq(true) }
451
- specify { expect(subject.import(cities + deleted) { |_data| false }).to eq(false) }
452
- specify do
453
- expect(subject.import(cities + deleted, batch_size: 1, &data_comparer.curry[cities[0].id])).to eq(false)
454
- end
455
- specify do
456
- expect(subject.import(cities + deleted, batch_size: 1, &data_comparer.curry[cities[1].id])).to eq(false)
457
- end
458
- specify do
459
- expect(subject.import(cities + deleted, batch_size: 1, &data_comparer.curry[cities[2].id])).to eq(false)
460
- end
461
- specify do
462
- expect(subject.import(cities + deleted, batch_size: 1, &data_comparer.curry[deleted[0].id])).to eq(false)
463
- end
464
- specify do
465
- expect(subject.import(cities + deleted, batch_size: 1, &data_comparer.curry[deleted[1].id])).to eq(false)
466
- end
467
- end
468
-
469
- context 'ids' do
470
- specify { expect(subject.import(ids) { |_data| true }).to eq(true) }
471
- specify { expect(subject.import(ids) { |_data| false }).to eq(false) }
472
- specify { expect(subject.import(ids, batch_size: 1, &data_comparer.curry[cities[0].id])).to eq(false) }
473
- specify { expect(subject.import(ids, batch_size: 1, &data_comparer.curry[cities[1].id])).to eq(false) }
474
- specify { expect(subject.import(ids, batch_size: 1, &data_comparer.curry[cities[2].id])).to eq(false) }
475
- specify { expect(subject.import(ids, batch_size: 1, &data_comparer.curry[deleted[0].id])).to eq(false) }
476
- specify { expect(subject.import(ids, batch_size: 1, &data_comparer.curry[deleted[1].id])).to eq(false) }
477
- end
478
- end
479
- end
480
-
481
- describe '#import_fields' do
482
- subject { described_class.new(Country) }
483
- let!(:countries) { Array.new(3) { |i| Country.create!(rating: i) { |c| c.id = i + 1 } } }
484
- let!(:cities) { Array.new(6) { |i| City.create!(rating: i + 3, country_id: (i + 4) / 2) { |c| c.id = i + 3 } } }
485
-
486
- specify { expect(subject.import_fields).to match([contain_exactly(1, 2, 3)]) }
487
- specify { expect(subject.import_fields(fields: [:rating])).to match([contain_exactly([1, 0], [2, 1], [3, 2])]) }
488
-
489
- context 'scopes' do
490
- context do
491
- subject { described_class.new(Country.includes(:cities)) }
492
-
493
- specify { expect(subject.import_fields).to match([contain_exactly(1, 2, 3)]) }
494
- specify { expect(subject.import_fields(fields: [:rating])).to match([contain_exactly([1, 0], [2, 1], [3, 2])]) }
495
- end
496
-
497
- context do
498
- subject { described_class.new(Country.joins(:cities)) }
499
-
500
- specify { expect(subject.import_fields).to match([contain_exactly(2, 3)]) }
501
- specify { expect(subject.import_fields(fields: [:rating])).to match([contain_exactly([2, 1], [3, 2])]) }
502
- end
503
-
504
- context 'ignores default scope if another scope is passed' do
505
- subject { described_class.new(Country.joins(:cities)) }
506
-
507
- specify { expect(subject.import_fields(Country.where('rating < 2'))).to match([contain_exactly(1, 2)]) }
508
- specify do
509
- expect(subject.import_fields(Country.where('rating < 2'), fields: [:rating]))
510
- .to match([contain_exactly([1, 0], [2, 1])])
511
- end
512
- end
513
- end
514
-
515
- context 'objects/ids' do
516
- specify { expect(subject.import_fields(1, 2)).to match([contain_exactly(1, 2)]) }
517
- specify { expect(subject.import_fields(1, 2, fields: [:rating])).to match([contain_exactly([1, 0], [2, 1])]) }
518
-
519
- specify { expect(subject.import_fields(countries.first(2))).to match([contain_exactly(1, 2)]) }
520
- specify do
521
- expect(subject.import_fields(countries.first(2), fields: [:rating]))
522
- .to match([contain_exactly([1, 0], [2, 1])])
523
- end
524
- end
525
-
526
- context 'batch_size' do
527
- specify { expect(subject.import_fields(batch_size: 2)).to match([contain_exactly(1, 2), [3]]) }
528
- specify do
529
- expect(subject.import_fields(batch_size: 2, fields: [:rating]))
530
- .to match([contain_exactly([1, 0], [2, 1]), [[3, 2]]])
531
- end
532
-
533
- specify do
534
- expect(subject.import_fields(Country.where('rating < 2'), batch_size: 2))
535
- .to match([contain_exactly(1, 2)])
536
- end
537
- specify do
538
- expect(subject.import_fields(Country.where('rating < 2'), batch_size: 2, fields: [:rating]))
539
- .to match([contain_exactly([1, 0], [2, 1])])
540
- end
541
-
542
- specify { expect(subject.import_fields(1, 2, batch_size: 1)).to match([[1], [2]]) }
543
- specify { expect(subject.import_fields(1, 2, batch_size: 1, fields: [:rating])).to match([[[1, 0]], [[2, 1]]]) }
544
-
545
- specify { expect(subject.import_fields(countries.first(2), batch_size: 1)).to match([[1], [2]]) }
546
- specify do
547
- expect(subject.import_fields(countries.first(2), batch_size: 1, fields: [:rating]))
548
- .to match([[[1, 0]], [[2, 1]]])
549
- end
550
- end
551
-
552
- context 'typecast' do
553
- specify { expect(subject.import_fields(typecast: false)).to match([contain_exactly(1, 2, 3)]) }
554
- specify do
555
- expect(subject.import_fields(fields: [:updated_at]).to_a)
556
- .to match([contain_exactly(
557
- [1, an_instance_of(Time)],
558
- [2, an_instance_of(Time)],
559
- [3, an_instance_of(Time)]
560
- )])
561
- end
562
- specify do
563
- expect(subject.import_fields(fields: [:updated_at], typecast: false))
564
- .to match([contain_exactly(
565
- [1, match(/#{Time.now.utc.strftime('%Y-%m-%d')}/)],
566
- [2, match(/#{Time.now.utc.strftime('%Y-%m-%d')}/)],
567
- [3, match(/#{Time.now.utc.strftime('%Y-%m-%d')}/)]
568
- )])
569
- end
570
- end
571
- end
572
-
573
- describe '#load' do
574
- context do
575
- let!(:cities) { Array.new(3) { |i| City.create!(rating: i / 2) } }
576
- let!(:deleted) { Array.new(2) { City.create!.tap(&:destroy) } }
577
- let(:city_ids) { cities.map(&:id) }
578
- let(:deleted_ids) { deleted.map(&:id) }
579
-
580
- subject { described_class.new(City) }
581
-
582
- specify { expect(subject.load(city_ids, _index: 'users')).to eq(cities) }
583
- specify { expect(subject.load(city_ids.reverse, _index: 'users')).to eq(cities.reverse) }
584
- specify { expect(subject.load(deleted_ids, _index: 'users')).to eq([nil, nil]) }
585
- specify { expect(subject.load(city_ids + deleted_ids, _index: 'users')).to eq([*cities, nil, nil]) }
586
- specify do
587
- expect(subject.load(city_ids, _index: 'users', scope: -> { where(rating: 0) }))
588
- .to eq(cities.first(2) + [nil])
589
- end
590
- specify do
591
- expect(
592
- subject.load(city_ids, _index: 'users', scope: -> { where(rating: 0) }, users: {scope: -> { where(rating: 1) }})
593
- ).to eq([nil, nil] + cities.last(1))
594
- end
595
- specify do
596
- expect(subject.load(city_ids, _index: 'users', scope: City.where(rating: 1)))
597
- .to eq([nil, nil] + cities.last(1))
598
- end
599
- specify do
600
- expect(
601
- subject.load(city_ids, _index: 'users', scope: City.where(rating: 1), users: {scope: -> { where(rating: 0) }})
602
- ).to eq(cities.first(2) + [nil])
603
- end
604
- end
605
-
606
- context 'custom primary_key' do
607
- before { stub_model(:city) { self.primary_key = 'rating' } }
608
- let!(:cities) { Array.new(3) { |i| City.create!(country_id: i / 2) { |c| c.rating = i + 7 } } }
609
- let!(:deleted) { Array.new(2) { |i| City.create! { |c| c.rating = i + 10 }.tap(&:destroy) } }
610
- let(:city_ids) { cities.map(&:rating) }
611
- let(:deleted_ids) { deleted.map(&:rating) }
612
-
613
- subject { described_class.new(City) }
614
-
615
- specify { expect(subject.load(city_ids, _index: 'users')).to eq(cities) }
616
- specify { expect(subject.load(city_ids.reverse, _index: 'users')).to eq(cities.reverse) }
617
- specify { expect(subject.load(deleted_ids, _index: 'users')).to eq([nil, nil]) }
618
- specify { expect(subject.load(city_ids + deleted_ids, _index: 'users')).to eq([*cities, nil, nil]) }
619
- specify do
620
- expect(subject.load(city_ids, _index: 'users', scope: -> { where(country_id: 0) }))
621
- .to eq(cities.first(2) + [nil])
622
- end
623
- specify do
624
- expect(
625
- subject.load(
626
- city_ids, _index: 'users', scope: -> { where(country_id: 0) }, users: {scope: -> { where(country_id: 1) }}
627
- )
628
- ).to eq([nil, nil] + cities.last(1))
629
- end
630
- specify do
631
- expect(subject.load(city_ids, _index: 'users', scope: City.where(country_id: 1)))
632
- .to eq([nil, nil] + cities.last(1))
633
- end
634
- specify do
635
- expect(
636
- subject.load(
637
- city_ids, _index: 'users', scope: City.where(country_id: 1), users: {scope: -> { where(country_id: 0) }}
638
- )
639
- ).to eq(cities.first(2) + [nil])
640
- end
641
- end
642
-
643
- context 'with raw_import option' do
644
- subject { described_class.new(City) }
645
-
646
- let!(:cities) { Array.new(3) { |i| City.create!(rating: i / 2) } }
647
- let(:city_ids) { cities.map(&:id) }
648
-
649
- let(:raw_import) { ->(hash) { RawCity.new(hash['id']) } }
650
- it 'uses the custom loader' do
651
- raw_cities = subject.load(city_ids, _index: 'cities', raw_import: raw_import).map do |c|
652
- {id: c.id, rating: c.rating}
653
- end
654
-
655
- expect(raw_cities).to eq([
656
- {id: 1, rating: 10},
657
- {id: 2, rating: 20},
658
- {id: 3, rating: 30}
659
- ])
660
- end
661
- end
662
- end
663
- end