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,886 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe Chewy::Search::Request do
4
- before { drop_indices }
5
-
6
- before do
7
- stub_index(:products) do
8
- field :id, type: :integer
9
- field :name
10
- field :age, type: :integer
11
- end
12
-
13
- stub_index(:cities) do
14
- field :id, type: :integer
15
- end
16
-
17
- stub_index(:countries) do
18
- field :id, type: :integer
19
- end
20
- end
21
-
22
- subject { described_class.new(ProductsIndex) }
23
-
24
- describe '#==' do
25
- specify { expect(described_class.new(ProductsIndex)).to eq(described_class.new(ProductsIndex)) }
26
- specify { expect(described_class.new(ProductsIndex)).not_to eq(described_class.new(CitiesIndex)) }
27
- specify { expect(described_class.new(ProductsIndex)).not_to eq(described_class.new(ProductsIndex, CitiesIndex)) }
28
- specify do
29
- expect(described_class.new(CitiesIndex, ProductsIndex)).to eq(described_class.new(ProductsIndex, CitiesIndex))
30
- end
31
- specify do
32
- expect(described_class.new(ProductsIndex, CitiesIndex)).to eq(described_class.new(CitiesIndex, ProductsIndex))
33
- end
34
-
35
- specify { expect(described_class.new(ProductsIndex).limit(10)).to eq(described_class.new(ProductsIndex).limit(10)) }
36
- specify do
37
- expect(described_class.new(ProductsIndex).limit(10)).not_to eq(described_class.new(ProductsIndex).limit(20))
38
- end
39
-
40
- specify { expect(ProductsIndex.limit(10)).to eq(ProductsIndex.limit(10)) }
41
- specify { expect(ProductsIndex.limit(10)).not_to eq(CitiesIndex.limit(10)) }
42
- end
43
-
44
- describe '#render' do
45
- specify do
46
- expect(subject.render)
47
- .to match(
48
- index: %w[products],
49
- body: {}
50
- )
51
- end
52
- end
53
-
54
- describe '#inspect' do
55
- specify do
56
- expect(described_class.new(ProductsIndex).inspect)
57
- .to match(/\A<Chewy::Search::Request/)
58
- .and include('"products"')
59
- end
60
- specify do
61
- expect(ProductsIndex.limit(10).inspect)
62
- .to match(/\A<ProductsIndex::Query/)
63
- .and include('"products"')
64
- .and include('size')
65
- .and include('10')
66
- end
67
- end
68
-
69
- %i[query post_filter].each do |name|
70
- describe "##{name}" do
71
- specify do
72
- expect(subject.send(name, match: {foo: 'bar'}).render[:body])
73
- .to include(name => {match: {foo: 'bar'}})
74
- end
75
- specify { expect(subject.send(name, nil)).to be_a described_class }
76
- specify do
77
- expect(subject.send(name) { match foo: 'bar' }.render[:body])
78
- .to include(name => {match: {foo: 'bar'}})
79
- end
80
- specify do
81
- expect(subject.send(name, match: {foo: 'bar'}).send(name) { multi_match foo: 'bar' }.render[:body])
82
- .to include(name => {bool: {must: [{match: {foo: 'bar'}}, {multi_match: {foo: 'bar'}}]}})
83
- end
84
- specify { expect { subject.send(name, match: {foo: 'bar'}) }.not_to change { subject.render } }
85
- specify do
86
- expect(
87
- subject.send(name).should(match: {foo: 'bar'}).send(name).must_not { multi_match foo: 'bar' }.render[:body]
88
- ).to include(name => {bool: {should: {match: {foo: 'bar'}}, must_not: {multi_match: {foo: 'bar'}}}})
89
- end
90
-
91
- context do
92
- let(:other_scope) { subject.send(name).should { multi_match foo: 'bar' }.send(name) { match foo: 'bar' } }
93
-
94
- specify do
95
- expect(
96
- subject.send(name).not(other_scope).render[:body]
97
- ).to include(
98
- name => {
99
- bool: {
100
- must_not: {
101
- bool: {
102
- must: {match: {foo: 'bar'}},
103
- should: {multi_match: {foo: 'bar'}}
104
- }
105
- }
106
- }
107
- }
108
- )
109
- end
110
- end
111
- end
112
- end
113
-
114
- describe '#filter' do
115
- specify do
116
- expect(subject.filter(match: {foo: 'bar'}).render[:body])
117
- .to include(query: {bool: {filter: {match: {foo: 'bar'}}}})
118
- end
119
- specify { expect(subject.filter(nil)).to be_a described_class }
120
- specify do
121
- expect(subject.filter(match: {foo: 'bar'}).render[:body])
122
- .to include(query: {bool: {filter: {match: {foo: 'bar'}}}})
123
- end
124
- specify do
125
- expect(subject.filter(match: {foo: 'bar'}).filter { multi_match foo: 'bar' }.render[:body])
126
- .to include(query: {bool: {filter: [{match: {foo: 'bar'}}, {multi_match: {foo: 'bar'}}]}})
127
- end
128
- specify { expect { subject.filter(match: {foo: 'bar'}) }.not_to change { subject.render } }
129
- specify do
130
- expect(
131
- subject.filter.should(match: {foo: 'bar'}).filter.must_not { multi_match foo: 'bar' }.render[:body]
132
- ).to include(
133
- query: {
134
- bool: {
135
- filter: {
136
- bool: {
137
- should: {match: {foo: 'bar'}},
138
- must_not: {multi_match: {foo: 'bar'}}
139
- }
140
- }
141
- }
142
- }
143
- )
144
- end
145
-
146
- context do
147
- let(:other_scope) { subject.filter.should { multi_match foo: 'bar' }.filter { match foo: 'bar' } }
148
-
149
- specify do
150
- expect(
151
- subject.filter.not(other_scope).render[:body]
152
- ).to include(
153
- query: {
154
- bool: {
155
- filter: {
156
- bool: {
157
- must_not: {
158
- bool: {
159
- must: {match: {foo: 'bar'}},
160
- should: {multi_match: {foo: 'bar'}}
161
- }
162
- }
163
- }
164
- }
165
- }
166
- }
167
- )
168
- end
169
- end
170
- end
171
-
172
- {limit: :size, offset: :from, terminate_after: :terminate_after}.each do |name, param_name|
173
- describe "##{name}" do
174
- specify { expect(subject.send(name, 10).render[:body]).to include(param_name => 10) }
175
- specify { expect(subject.send(name, 10).send(name, 20).render[:body]).to include(param_name => 20) }
176
- specify { expect(subject.send(name, 10).send(name, nil).render[:body]).to be_blank }
177
- specify { expect { subject.send(name, 10) }.not_to change { subject.render } }
178
- end
179
- end
180
-
181
- describe '#order' do
182
- specify { expect(subject.order(:foo).render[:body]).to include(sort: ['foo']) }
183
- specify { expect(subject.order(foo: 42).order(nil).render[:body]).to include(sort: ['foo' => 42]) }
184
- specify { expect(subject.order(foo: 42).order(foo: 43).render[:body]).to include(sort: [{'foo' => 42}, {'foo' => 43}]) }
185
- specify { expect(subject.order(:foo).order(:bar, :baz).render[:body]).to include(sort: %w[foo bar baz]) }
186
- specify { expect(subject.order(nil).render[:body]).to be_blank }
187
- specify { expect { subject.order(:foo) }.not_to change { subject.render } }
188
- end
189
-
190
- describe '#reorder' do
191
- specify { expect(subject.reorder(:foo).render[:body]).to include(sort: ['foo']) }
192
- specify { expect(subject.reorder(:foo).reorder(:bar, :baz).render[:body]).to include(sort: %w[bar baz]) }
193
- specify { expect(subject.reorder(foo: 42).reorder(foo: 43).render[:body]).to include(sort: ['foo' => 43]) }
194
- specify { expect(subject.reorder(foo: 42).reorder(nil).render[:body]).to be_blank }
195
- specify { expect(subject.reorder(nil).render[:body]).to be_blank }
196
- specify { expect { subject.reorder(:foo) }.not_to change { subject.render } }
197
- end
198
-
199
- %i[track_scores track_total_hits explain version profile].each do |name|
200
- describe "##{name}" do
201
- specify { expect(subject.send(name).render[:body]).to include(name => true) }
202
- specify { expect(subject.send(name).send(name, false).render[:body]).to be_blank }
203
- specify { expect { subject.send(name) }.not_to change { subject.render } }
204
- end
205
- end
206
-
207
- describe '#request_cache' do
208
- specify { expect(subject.request_cache(true).render).to include(request_cache: true) }
209
- specify { expect(subject.request_cache(true).request_cache(false).render).to include(request_cache: false) }
210
- specify { expect(subject.request_cache(true).request_cache(nil).render[:request_cache]).to be_blank }
211
- specify { expect { subject.request_cache(true) }.not_to change { subject.render } }
212
- end
213
-
214
- describe '#search_type' do
215
- specify { expect(subject.search_type('foo').render).to include(search_type: 'foo') }
216
- specify { expect(subject.search_type('foo').search_type('bar').render).to include(search_type: 'bar') }
217
- specify { expect(subject.search_type('foo').search_type(nil).render[:search_type]).to be_blank }
218
- specify { expect { subject.search_type('foo') }.not_to change { subject.render } }
219
- end
220
-
221
- describe '#preference' do
222
- specify { expect(subject.preference('foo').render).to include(preference: 'foo') }
223
- specify { expect(subject.preference('foo').preference('bar').render).to include(preference: 'bar') }
224
- specify { expect(subject.preference('foo').preference(nil).render[:preference]).to be_blank }
225
- specify { expect { subject.preference('foo') }.not_to change { subject.render } }
226
- end
227
-
228
- describe '#timeout' do
229
- specify { expect(subject.timeout(:foo).render[:body]).to include(timeout: 'foo') }
230
- specify { expect(subject.timeout(:foo).timeout(:bar).render[:body]).to include(timeout: 'bar') }
231
- specify { expect(subject.timeout(:foo).timeout(nil).render[:body]).to be_blank }
232
- specify { expect { subject.timeout(:foo) }.not_to change { subject.render } }
233
- end
234
-
235
- describe '#source' do
236
- specify { expect(subject.source(:foo).render[:body]).to include(_source: ['foo']) }
237
- specify { expect(subject.source(:foo, :bar).source(nil).render[:body]).to include(_source: %w[foo bar]) }
238
- specify { expect(subject.source(%i[foo bar]).source(nil).render[:body]).to include(_source: %w[foo bar]) }
239
- specify { expect(subject.source(excludes: :foo).render[:body]).to include(_source: {excludes: %w[foo]}) }
240
- specify do
241
- expect(subject.source(excludes: :foo).source(excludes: %i[foo bar]).render[:body])
242
- .to include(_source: {excludes: %w[foo bar]})
243
- end
244
- specify do
245
- expect(subject.source(excludes: :foo).source(excludes: %i[foo bar]).render[:body])
246
- .to include(_source: {excludes: %w[foo bar]})
247
- end
248
- specify do
249
- expect(subject.source(excludes: :foo).source(:bar).render[:body])
250
- .to include(_source: {includes: %w[bar], excludes: %w[foo]})
251
- end
252
- specify { expect(subject.source(excludes: :foo).source(false).render[:body]).to include(_source: false) }
253
- specify do
254
- expect(subject.source(excludes: :foo).source(false).source(excludes: :bar).render[:body])
255
- .to include(_source: {excludes: %w[foo bar]})
256
- end
257
- specify do
258
- expect(subject.source(excludes: :foo).source(false).source(true).render[:body])
259
- .to include(_source: {excludes: %w[foo]})
260
- end
261
- specify { expect(subject.source(nil).render[:body]).to be_blank }
262
- specify { expect { subject.source(:foo) }.not_to change { subject.render } }
263
- end
264
-
265
- describe '#stored_fields' do
266
- specify { expect(subject.stored_fields(:foo).render[:body]).to include(stored_fields: ['foo']) }
267
- specify do
268
- expect(subject.stored_fields(%i[foo bar]).stored_fields(nil).render[:body])
269
- .to include(stored_fields: %w[foo bar])
270
- end
271
- specify do
272
- expect(subject.stored_fields(:foo).stored_fields(:foo, :bar).render[:body])
273
- .to include(stored_fields: %w[foo bar])
274
- end
275
- specify do
276
- expect(subject.stored_fields(:foo).stored_fields(false).render[:body])
277
- .to include(stored_fields: '_none_')
278
- end
279
- specify do
280
- expect(subject.stored_fields(:foo).stored_fields(false).stored_fields(:bar).render[:body])
281
- .to include(stored_fields: %w[foo bar])
282
- end
283
- specify do
284
- expect(subject.stored_fields(:foo).stored_fields(false).stored_fields(true).render[:body])
285
- .to include(stored_fields: %w[foo])
286
- end
287
- specify { expect(subject.stored_fields(nil).render[:body]).to be_blank }
288
- specify { expect { subject.stored_fields(:foo) }.not_to change { subject.render } }
289
- end
290
-
291
- %i[script_fields highlight].each do |name|
292
- describe "##{name}" do
293
- specify { expect(subject.send(name, foo: {bar: 42}).render[:body]).to include(name => {'foo' => {bar: 42}}) }
294
- specify do
295
- expect(subject.send(name, foo: {bar: 42}).send(name, moo: {baz: 43}).render[:body])
296
- .to include(name => {'foo' => {bar: 42}, 'moo' => {baz: 43}})
297
- end
298
- specify do
299
- expect(subject.send(name, foo: {bar: 42}).send(name, nil).render[:body])
300
- .to include(name => {'foo' => {bar: 42}})
301
- end
302
- specify { expect { subject.send(name, foo: {bar: 42}) }.not_to change { subject.render } }
303
- end
304
- end
305
-
306
- %i[suggest aggs].each do |name|
307
- describe "##{name}" do
308
- specify { expect(subject.send(name, foo: {bar: 42}).render[:body]).to include(name => {'foo' => {bar: 42}}) }
309
- specify do
310
- expect(subject.send(name, foo: {bar: 42}).send(name, moo: {baz: 43}).render[:body])
311
- .to include(name => {'foo' => {bar: 42}, 'moo' => {baz: 43}})
312
- end
313
- specify do
314
- expect(subject.send(name, foo: {bar: 42}).send(name, nil).render[:body])
315
- .to include(name => {'foo' => {bar: 42}})
316
- end
317
- specify { expect { subject.send(name, foo: {bar: 42}) }.not_to change { subject.render } }
318
- end
319
- end
320
-
321
- %i[collapse knn].each do |name|
322
- describe "##{name}" do
323
- specify { expect(subject.send(name, foo: {bar: 42}).render[:body]).to include(name => {'foo' => {bar: 42}}) }
324
- specify do
325
- expect(subject.send(name, foo: {bar: 42}).send(name, moo: {baz: 43}).render[:body])
326
- .to include(name => {'moo' => {baz: 43}})
327
- end
328
- specify { expect(subject.send(name, foo: {bar: 42}).send(name, nil).render[:body]).to be_blank }
329
- specify { expect { subject.send(name, foo: {bar: 42}) }.not_to change { subject.render } }
330
- end
331
- end
332
-
333
- describe '#docvalue_fields' do
334
- specify { expect(subject.docvalue_fields(:foo).render[:body]).to include(docvalue_fields: ['foo']) }
335
- specify do
336
- expect(subject.docvalue_fields(%i[foo bar]).docvalue_fields(nil).render[:body])
337
- .to include(docvalue_fields: %w[foo bar])
338
- end
339
- specify do
340
- expect(subject.docvalue_fields(:foo).docvalue_fields(:foo, :bar).render[:body])
341
- .to include(docvalue_fields: %w[foo bar])
342
- end
343
- specify { expect(subject.docvalue_fields(nil).render[:body]).to be_blank }
344
- specify { expect { subject.docvalue_fields(:foo) }.not_to change { subject.render } }
345
- end
346
-
347
- describe '#indices' do
348
- specify { expect(described_class.new(:products).render[:index]).to eq(%w[products]) }
349
- specify { expect(subject.indices(:cities).render[:index]).to eq(%w[cities products]) }
350
- specify { expect(subject.indices(CitiesIndex, :whatever).render[:index]).to eq(%w[cities products whatever]) }
351
- specify { expect(subject.indices([CitiesIndex, :products]).render[:index]).to eq(%w[cities products]) }
352
- specify { expect { subject.indices(:cities) }.not_to change { subject.render } }
353
- end
354
-
355
- describe '#indices_boost' do
356
- specify { expect(subject.indices_boost(foo: 1.2).render[:body]).to include(indices_boost: [{'foo' => 1.2}]) }
357
- specify do
358
- expect(subject.indices_boost(foo: 1.2).indices_boost(moo: 1.3).render[:body])
359
- .to include(indices_boost: [{'foo' => 1.2}, {'moo' => 1.3}])
360
- end
361
- specify do
362
- expect(subject.indices_boost(foo: 1.2).indices_boost(nil).render[:body])
363
- .to include(indices_boost: [{'foo' => 1.2}])
364
- end
365
- specify { expect { subject.indices_boost(foo: 1.2) }.not_to change { subject.render } }
366
- end
367
-
368
- describe '#rescore' do
369
- specify { expect(subject.rescore(foo: 42).render[:body]).to include(rescore: [{foo: 42}]) }
370
- specify do
371
- expect(subject.rescore(foo: 42).rescore(moo: 43).render[:body])
372
- .to include(rescore: [{foo: 42}, {moo: 43}])
373
- end
374
- specify { expect(subject.rescore(foo: 42).rescore(nil).render[:body]).to include(rescore: [{foo: 42}]) }
375
- specify { expect { subject.rescore(foo: 42) }.not_to change { subject.render } }
376
- end
377
-
378
- describe '#min_score' do
379
- specify { expect(subject.min_score(1.2).render[:body]).to include(min_score: 1.2) }
380
- specify { expect(subject.min_score(1.2).min_score(0.5).render[:body]).to include(min_score: 0.5) }
381
- specify { expect(subject.min_score(1.2).min_score(nil).render[:body]).to be_blank }
382
- specify { expect { subject.min_score(1.2) }.not_to change { subject.render } }
383
- end
384
-
385
- describe '#ignore_unavailable' do
386
- specify { expect(subject.ignore_unavailable(true).render).to include(ignore_unavailable: true) }
387
- specify { expect(subject.ignore_unavailable(true).ignore_unavailable(false).render).to include(ignore_unavailable: false) }
388
- specify { expect(subject.ignore_unavailable(true).ignore_unavailable(nil).render[:ignore_unavailable]).to be_blank }
389
- specify { expect { subject.ignore_unavailable(true) }.not_to change { subject.render } }
390
- end
391
-
392
- describe '#search_after' do
393
- specify { expect(subject.search_after(:foo, :bar).render[:body]).to include(search_after: %i[foo bar]) }
394
- specify do
395
- expect(subject.search_after(%i[foo bar]).search_after(:baz).render[:body])
396
- .to include(search_after: [:baz])
397
- end
398
- specify { expect(subject.search_after(:foo).search_after(nil).render[:body]).to be_blank }
399
- specify { expect { subject.search_after(:foo) }.not_to change { subject.render } }
400
- end
401
-
402
- context 'loading', :orm do
403
- before do
404
- stub_model(:city)
405
- stub_model(:country)
406
-
407
- stub_index(:places) do
408
- index_scope City
409
- field :rating, type: 'integer'
410
- end
411
-
412
- stub_index(:countries) do
413
- index_scope Country
414
- field :rating, type: 'integer'
415
- end
416
- end
417
-
418
- before { PlacesIndex.import!(cities) }
419
-
420
- let(:cities) { Array.new(2) { |i| City.create!(rating: i) } }
421
- let(:countries) { Array.new(2) { |i| Country.create!(rating: i + 2) } }
422
-
423
- subject { described_class.new(PlacesIndex).order(:rating) }
424
-
425
- describe '#objects' do
426
- specify { expect(subject.objects).to eq([*cities]) }
427
- specify { expect(subject.objects.class).to eq(Array) }
428
- end
429
-
430
- describe '#load' do
431
- specify { expect(subject.load(only: 'city')).to eq([*cities]) }
432
- specify { expect(subject.load(only: 'city').map(&:class).uniq).to eq([PlacesIndex]) }
433
- specify { expect(subject.load(only: 'city').objects).to eq([*cities]) }
434
- end
435
- end
436
-
437
- describe '#merge' do
438
- let(:first) { described_class.new(ProductsIndex).limit(10).offset(10) }
439
- let(:second) { described_class.new(ProductsIndex).offset(20).order(:foo) }
440
-
441
- specify { expect(first.merge(second)).to eq(described_class.new(ProductsIndex).limit(10).offset(20).order(:foo)) }
442
- specify { expect { first.merge(second) }.not_to change { first.render } }
443
- specify { expect { first.merge(second) }.not_to change { second.render } }
444
-
445
- specify { expect(second.merge(first)).to eq(described_class.new(ProductsIndex).limit(10).offset(10).order(:foo)) }
446
- specify { expect { second.merge(first) }.not_to change { first.render } }
447
- specify { expect { second.merge(first) }.not_to change { second.render } }
448
- end
449
-
450
- context do
451
- let(:first_scope) { subject.query(foo: 'bar').filter.should(moo: 'baz').post_filter.must_not(boo: 'baf').limit(10) }
452
- let(:second_scope) do
453
- subject.filter(foo: 'bar').post_filter.should(moo: 'baz').query.must_not(boo: 'baf').limit(20)
454
- end
455
-
456
- describe '#and' do
457
- specify do
458
- expect(first_scope.and(second_scope).render[:body]).to eq(
459
- query: {bool: {
460
- must: [{foo: 'bar'}, {bool: {must_not: {boo: 'baf'}}}],
461
- filter: [{bool: {should: {moo: 'baz'}}}, {foo: 'bar'}]
462
- }},
463
- post_filter: {bool: {must: [{bool: {must_not: {boo: 'baf'}}}, {bool: {should: {moo: 'baz'}}}]}},
464
- size: 10
465
- )
466
- end
467
- specify { expect { first_scope.and(second_scope) }.not_to change { first_scope.render } }
468
- specify { expect { first_scope.and(second_scope) }.not_to change { second_scope.render } }
469
- end
470
-
471
- describe '#or' do
472
- specify do
473
- expect(first_scope.or(second_scope).render[:body]).to eq(
474
- query: {bool: {
475
- should: [{foo: 'bar'}, {bool: {must_not: {boo: 'baf'}}}],
476
- filter: {bool: {should: [{bool: {should: {moo: 'baz'}}}, {foo: 'bar'}]}}
477
- }},
478
- post_filter: {bool: {should: [{bool: {must_not: {boo: 'baf'}}}, {bool: {should: {moo: 'baz'}}}]}},
479
- size: 10
480
- )
481
- end
482
- specify { expect { first_scope.or(second_scope) }.not_to change { first_scope.render } }
483
- specify { expect { first_scope.or(second_scope) }.not_to change { second_scope.render } }
484
- end
485
-
486
- describe '#not' do
487
- specify do
488
- expect(first_scope.not(second_scope).render[:body]).to eq(
489
- query: {bool: {
490
- must: {foo: 'bar'}, must_not: {bool: {must_not: {boo: 'baf'}}},
491
- filter: {bool: {should: {moo: 'baz'}, must_not: {foo: 'bar'}}}
492
- }},
493
- post_filter: {bool: {must_not: [{boo: 'baf'}, {bool: {should: {moo: 'baz'}}}]}},
494
- size: 10
495
- )
496
- end
497
- specify { expect { first_scope.not(second_scope) }.not_to change { first_scope.render } }
498
- specify { expect { first_scope.not(second_scope) }.not_to change { second_scope.render } }
499
- end
500
- end
501
-
502
- describe '#only' do
503
- subject { described_class.new(ProductsIndex).limit(10).offset(10) }
504
-
505
- specify { expect(subject.only(:limit)).to eq(described_class.new(ProductsIndex).limit(10)) }
506
- specify { expect { subject.only(:limit) }.not_to change { subject.render } }
507
- end
508
-
509
- describe '#except' do
510
- subject { described_class.new(ProductsIndex).limit(10).offset(10) }
511
-
512
- specify { expect(subject.except(:limit)).to eq(described_class.new(ProductsIndex).offset(10)) }
513
- specify { expect { subject.except(:limit) }.not_to change { subject.render } }
514
- end
515
-
516
- context 'index does not exist' do
517
- specify { expect(subject).to eq([]) }
518
- specify { expect(subject.count).to eq(0) }
519
- end
520
-
521
- context 'integration' do
522
- let(:products_count) { 9 }
523
- let(:products) do
524
- Array.new(products_count) do |i|
525
- {id: i.next.to_i, name: "Name#{i.next}", age: 10 * i.next}.stringify_keys!
526
- end
527
- end
528
- let(:cities) { Array.new(3) { |i| {id: (i.next + 9).to_i}.stringify_keys! } }
529
- let(:countries) { Array.new(3) { |i| {id: (i.next + 12).to_i}.stringify_keys! } }
530
- before do
531
- ProductsIndex.import!(products.map { |h| double(h) })
532
- CountriesIndex.import!(countries.map { |h| double(h) })
533
- CitiesIndex.import!(cities.map { |h| double(h) })
534
- end
535
-
536
- specify { expect(subject[0]._data).to be_a Hash }
537
-
538
- context 'another index' do
539
- subject { described_class.new(CitiesIndex) }
540
-
541
- specify { expect(subject.count).to eq(3) }
542
- specify { expect(subject.size).to eq(3) }
543
- end
544
-
545
- context 'mixed indexes' do
546
- subject { described_class.new(CitiesIndex, ProductsIndex) }
547
-
548
- specify { expect(subject.count).to eq(12) }
549
- specify { expect(subject.size).to eq(10) } # pagination limit
550
- end
551
-
552
- context 'instrumentation' do
553
- specify do
554
- outer_payload = nil
555
- ActiveSupport::Notifications.subscribe('search_query.chewy') do |_name, _start, _finish, _id, payload|
556
- outer_payload = payload
557
- end
558
- subject.query(match: {name: 'name3'}).to_a
559
- expect(outer_payload).to eq(
560
- index: ProductsIndex,
561
- indexes: [ProductsIndex],
562
- request: {index: ['products'], body: {query: {match: {name: 'name3'}}}}
563
- )
564
- end
565
- end
566
-
567
- describe '#none' do
568
- specify { expect(subject.none).to eq([]) }
569
- end
570
-
571
- describe '#highlight' do
572
- specify { expect(subject.query(match: {name: 'name3'}).highlight(fields: {name: {}}).first.name).to eq('Name3') }
573
- specify do
574
- expect(subject.query(match: {name: 'name3'}).highlight(fields: {name: {}}).first.name_highlight)
575
- .to eq('<em>Name3</em>')
576
- end
577
- specify do
578
- expect(subject.query(match: {name: 'name3'}).highlight(fields: {name: {}}).first.name_highlights)
579
- .to eq(['<em>Name3</em>'])
580
- end
581
- specify do
582
- expect(subject.query(match: {name: 'name3'}).highlight(fields: {name: {}}).first._data['_source']['name'])
583
- .to eq('Name3')
584
- end
585
- end
586
-
587
- describe '#suggest' do
588
- let(:products_count) { 3 }
589
-
590
- specify do
591
- expect(subject.suggest(
592
- foo: {
593
- text: 'name',
594
- term: {field: 'name'}
595
- }
596
- ).suggest).to eq(
597
- 'foo' => [
598
- {'text' => 'name', 'offset' => 0, 'length' => 4, 'options' => [
599
- {'text' => 'name1', 'score' => 0.75, 'freq' => 1},
600
- {'text' => 'name2', 'score' => 0.75, 'freq' => 1},
601
- {'text' => 'name3', 'score' => 0.75, 'freq' => 1}
602
- ]}
603
- ]
604
- )
605
- end
606
- end
607
-
608
- describe '#aggs' do
609
- specify do
610
- expect(subject.aggs(avg_age: {avg: {field: :age}}).aggs)
611
- .to eq('avg_age' => {'value' => 50.0})
612
- end
613
- end
614
-
615
- describe '#size' do
616
- specify { expect(subject.size).to eq(9) }
617
- specify { expect(subject.limit(6).size).to eq(6) }
618
- specify { expect(subject.offset(6).size).to eq(3) }
619
- end
620
-
621
- describe '#total' do
622
- specify { expect(subject.limit(6).total).to eq(9) }
623
- specify { expect(subject.limit(6).total_count).to eq(9) }
624
- specify { expect(subject.offset(6).total_entries).to eq(9) }
625
- end
626
-
627
- describe '#count' do
628
- specify { expect(subject.count).to eq(9) }
629
- specify { expect(subject.limit(6).count).to eq(9) }
630
- specify { expect(subject.offset(6).count).to eq(9) }
631
- specify { expect(subject.indices(:products, CountriesIndex).count).to eq(12) }
632
- specify { expect(subject.filter(term: {age: 10}).count).to eq(1) }
633
- specify { expect(subject.query(term: {age: 10}).count).to eq(1) }
634
- specify { expect(subject.order(nil).count).to eq(9) }
635
- specify { expect(subject.none.count).to eq(0) }
636
-
637
- context do
638
- before { expect(Chewy.client).to receive(:count).and_call_original }
639
- specify { subject.count }
640
- end
641
-
642
- context do
643
- subject { described_class.new(ProductsIndex).limit(6) }
644
- before do
645
- expect(Chewy.client).not_to receive(:count)
646
- subject.total
647
- end
648
- specify { expect(subject.count).to eq(9) }
649
- end
650
- end
651
-
652
- describe '#exists?' do
653
- before { expect(Chewy.client).to receive(:search).once.and_call_original }
654
-
655
- specify { expect(subject.exists?).to be(true) }
656
- specify { expect(subject.filter(match: {name: 'foo'}).exist?).to be(false) }
657
-
658
- context do
659
- before { subject.total }
660
- specify { expect(subject.exists?).to eq(true) }
661
- end
662
- end
663
-
664
- describe '#first' do
665
- subject { described_class.new(ProductsIndex).order(id: {order: 'desc'}) }
666
-
667
- context do
668
- before { expect(Chewy.client).to receive(:search).once.and_call_original }
669
-
670
- specify { expect(subject.first).to be_a(ProductsIndex).and have_attributes(id: 9) }
671
- specify { expect(subject.first(3).map(&:id)).to eq([9, 8, 7]) }
672
- specify { expect(subject.first(10).map(&:id)).to have(9).items }
673
- specify { expect(subject.limit(5).first(10).map(&:id)).to have(9).items }
674
- specify { expect(subject.terminate_after(5).first(10).map(&:id)).to have(5).items }
675
- end
676
-
677
- context do
678
- before do
679
- subject.response
680
- expect(Chewy.client).not_to receive(:search)
681
- end
682
-
683
- specify { expect(subject.first).to be_a(ProductsIndex).and have_attributes(id: 9) }
684
- specify { expect(subject.first(3).map(&:id)).to eq([9, 8, 7]) }
685
- specify { expect(subject.first(10).map(&:id)).to have(9).items }
686
-
687
- context do
688
- subject { described_class.new(ProductsIndex).terminate_after(5) }
689
- specify { expect(subject.first(10).map(&:id)).to have(5).items }
690
- end
691
- end
692
-
693
- context do
694
- subject { described_class.new(ProductsIndex).limit(5) }
695
- before do
696
- subject.response
697
- expect(Chewy.client).to receive(:search).once.and_call_original
698
- end
699
- specify { expect(subject.first(10).map(&:id)).to have(9).items }
700
- end
701
- end
702
-
703
- describe '#find' do
704
- specify { expect(subject.find('1')).to be_a(ProductsIndex).and have_attributes(id: 1) }
705
- specify { expect(subject.find { |w| w.id == 2 }).to be_a(ProductsIndex).and have_attributes(id: 2) }
706
- specify { expect(subject.limit(2).find('1', '3', '7').map(&:id)).to contain_exactly(1, 3, 7) }
707
- specify { expect(subject.find(1, 3, 7).map(&:id)).to contain_exactly(1, 3, 7) }
708
- specify do
709
- expect { subject.find('1', '3', '42') }
710
- .to raise_error Chewy::DocumentNotFound, 'Could not find documents for ids: 42'
711
- end
712
- specify do
713
- expect { subject.find(1, 3, 42) }
714
- .to raise_error Chewy::DocumentNotFound, 'Could not find documents for ids: 42'
715
- end
716
- specify do
717
- expect { subject.query(match: {name: 'name3'}).find('1', '3') }
718
- .to raise_error Chewy::DocumentNotFound, 'Could not find documents for ids: 1'
719
- end
720
- specify do
721
- expect { subject.query(match: {name: 'name2'}).find('1', '3') }
722
- .to raise_error Chewy::DocumentNotFound, 'Could not find documents for ids: 1 and 3'
723
- end
724
- specify do
725
- expect { subject.filter(match: {name: 'name2'}).find('1', '3') }
726
- .to raise_error Chewy::DocumentNotFound, 'Could not find documents for ids: 1 and 3'
727
- end
728
- specify do
729
- expect { subject.post_filter(match: {name: 'name2'}).find('1', '3') }
730
- .to raise_error Chewy::DocumentNotFound, 'Could not find documents for ids: 1 and 3'
731
- end
732
-
733
- context 'make sure it returns everything' do
734
- let(:products_count) { 12 }
735
- before { expect(Chewy.client).not_to receive(:scroll) }
736
-
737
- specify { expect(subject.find((1..12).to_a)).to have(12).items }
738
- end
739
-
740
- context 'make sure it returns everything in batches if needed' do
741
- before { stub_const("#{described_class}::DEFAULT_BATCH_SIZE", 5) }
742
- before { expect(Chewy.client).to receive(:scroll).once.and_call_original }
743
-
744
- specify { expect(subject.find((1..9).to_a)).to have(9).items }
745
- specify { expect(subject.find((1..9).to_a)).to all be_a(Chewy::Index) }
746
- end
747
- end
748
-
749
- describe '#pluck' do
750
- specify { expect(subject.limit(5).pluck(:_id)).to eq(%w[1 2 3 4 5]) }
751
- specify do
752
- expect(subject.limit(5).pluck(:_id, :age)).to eq([['1', 10], ['2', 20], ['3', 30], ['4', 40], ['5', 50]])
753
- end
754
- specify do
755
- expect(subject.limit(5).source(:name).pluck(:id, :age)).to eq([[1, 10], [2, 20], [3, 30], [4, 40], [5, 50]])
756
- end
757
- specify do
758
- expect(subject.limit(5).pluck(:_index, :name)).to eq([
759
- %w[products Name1],
760
- %w[products Name2],
761
- %w[products Name3],
762
- %w[products Name4],
763
- %w[products Name5]
764
- ])
765
- end
766
-
767
- context 'make sure it returns everything in batches if needed' do
768
- before { stub_const("#{described_class}::DEFAULT_PLUCK_BATCH_SIZE", 5) }
769
- before { expect(Chewy.client).to receive(:scroll).once.and_call_original }
770
-
771
- specify { expect(subject.pluck(:_id)).to eq((1..9).to_a.map(&:to_s)) }
772
- end
773
- end
774
-
775
- describe '#delete_all' do
776
- specify do
777
- expect do
778
- subject.none.delete_all
779
- Chewy.client.indices.refresh(index: 'products')
780
- end.not_to change { described_class.new(ProductsIndex).total }.from(9)
781
- end
782
- specify do
783
- expect do
784
- subject.query(match: {name: 'name3'}).delete_all
785
- Chewy.client.indices.refresh(index: 'products')
786
- end.to change { described_class.new(ProductsIndex).total }.from(9).to(8)
787
- end
788
- specify do
789
- expect do
790
- subject.filter(range: {age: {gte: 10, lte: 20}}).delete_all
791
- Chewy.client.indices.refresh(index: 'products')
792
- end.to change { described_class.new(ProductsIndex).total_count }.from(9).to(7)
793
- end
794
- specify do
795
- expect do
796
- subject.delete_all
797
- Chewy.client.indices.refresh(index: 'products')
798
- end.to change { described_class.new(ProductsIndex).total }.from(9).to(0)
799
- end
800
-
801
- specify do
802
- outer_payload = nil
803
- ActiveSupport::Notifications.subscribe('delete_query.chewy') do |_name, _start, _finish, _id, payload|
804
- outer_payload = payload
805
- end
806
- subject.query(match: {name: 'name3'}).delete_all
807
- expect(outer_payload).to eq(
808
- index: ProductsIndex,
809
- indexes: [ProductsIndex],
810
- request: {index: ['products'], body: {query: {match: {name: 'name3'}}}, refresh: true}
811
- )
812
- end
813
-
814
- specify do
815
- outer_payload = nil
816
- ActiveSupport::Notifications.subscribe('delete_query.chewy') do |_name, _start, _finish, _id, payload|
817
- outer_payload = payload
818
- end
819
- subject.query(match: {name: 'name3'}).delete_all(refresh: false)
820
- expect(outer_payload).to eq(
821
- index: ProductsIndex,
822
- indexes: [ProductsIndex],
823
- request: {index: ['products'], body: {query: {match: {name: 'name3'}}}, refresh: false}
824
- )
825
- end
826
-
827
- it 'delete records asynchronously' do
828
- outer_payload = nil
829
- ActiveSupport::Notifications.subscribe('delete_query.chewy') do |_name, _start, _finish, _id, payload|
830
- outer_payload = payload
831
- end
832
- subject.query(match: {name: 'name3'}).delete_all(
833
- refresh: false,
834
- wait_for_completion: false,
835
- requests_per_second: 10.0,
836
- scroll_size: 2000
837
- )
838
- expect(outer_payload).to eq(
839
- index: ProductsIndex,
840
- indexes: [ProductsIndex],
841
- request: {
842
- index: ['products'],
843
- body: {query: {match: {name: 'name3'}}},
844
- refresh: false,
845
- wait_for_completion: false,
846
- requests_per_second: 10.0,
847
- scroll_size: 2000
848
- }
849
- )
850
- end
851
- end
852
-
853
- describe '#response=' do
854
- let(:query) { ProductsIndex.limit(0) }
855
- let(:raw_response) { Chewy.client.search(query.render) }
856
-
857
- it 'wraps and assigns the raw response' do
858
- query.response = raw_response
859
- expect(query.response).to be_a(Chewy::Search::Response)
860
- end
861
- end
862
-
863
- describe '#performed?' do
864
- let(:query) { ProductsIndex.limit(0) }
865
- let(:raw_response) { Chewy.client.search(query.render) }
866
-
867
- it 'is false on a new query' do
868
- expect(query.performed?).to eq(false)
869
- end
870
-
871
- it 'is true after the search request was issued' do
872
- expect do
873
- # The `response` method has a side effect of performing unperformed
874
- # queries.
875
- query.response
876
- end.to change(query, :performed?).from(false).to(true)
877
- end
878
-
879
- it 'is true after assigning a raw response' do
880
- expect do
881
- query.response = raw_response
882
- end.to change(query, :performed?).from(false).to(true)
883
- end
884
- end
885
- end
886
- end