chewy 7.2.4 → 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 (86) hide show
  1. checksums.yaml +4 -4
  2. data/.github/CODEOWNERS +1 -0
  3. data/.github/dependabot.yml +42 -0
  4. data/.github/workflows/ruby.yml +26 -32
  5. data/.rubocop.yml +4 -1
  6. data/CHANGELOG.md +144 -0
  7. data/Gemfile +4 -4
  8. data/README.md +165 -10
  9. data/chewy.gemspec +4 -17
  10. data/gemfiles/base.gemfile +12 -0
  11. data/gemfiles/rails.6.1.activerecord.gemfile +2 -1
  12. data/gemfiles/rails.7.0.activerecord.gemfile +2 -1
  13. data/gemfiles/{rails.5.2.activerecord.gemfile → rails.7.1.activerecord.gemfile} +6 -3
  14. data/lib/chewy/config.rb +22 -14
  15. data/lib/chewy/elastic_client.rb +31 -0
  16. data/lib/chewy/errors.rb +5 -2
  17. data/lib/chewy/fields/base.rb +1 -1
  18. data/lib/chewy/fields/root.rb +1 -1
  19. data/lib/chewy/index/adapter/active_record.rb +13 -3
  20. data/lib/chewy/index/adapter/object.rb +3 -3
  21. data/lib/chewy/index/adapter/orm.rb +2 -2
  22. data/lib/chewy/index/crutch.rb +15 -7
  23. data/lib/chewy/index/import/bulk_builder.rb +6 -7
  24. data/lib/chewy/index/import/routine.rb +1 -1
  25. data/lib/chewy/index/import.rb +31 -4
  26. data/lib/chewy/index/observe/active_record_methods.rb +87 -0
  27. data/lib/chewy/index/observe/callback.rb +34 -0
  28. data/lib/chewy/index/observe.rb +3 -58
  29. data/lib/chewy/index/syncer.rb +1 -1
  30. data/lib/chewy/index.rb +25 -0
  31. data/lib/chewy/journal.rb +17 -6
  32. data/lib/chewy/log_subscriber.rb +5 -1
  33. data/lib/chewy/minitest/helpers.rb +1 -1
  34. data/lib/chewy/minitest/search_index_receiver.rb +3 -1
  35. data/lib/chewy/rake_helper.rb +74 -13
  36. data/lib/chewy/rspec/update_index.rb +13 -6
  37. data/lib/chewy/runtime/version.rb +1 -1
  38. data/lib/chewy/search/parameters/collapse.rb +16 -0
  39. data/lib/chewy/search/parameters/indices.rb +1 -1
  40. data/lib/chewy/search/parameters/knn.rb +16 -0
  41. data/lib/chewy/search/parameters/storage.rb +1 -1
  42. data/lib/chewy/search/parameters.rb +3 -3
  43. data/lib/chewy/search/request.rb +45 -11
  44. data/lib/chewy/search.rb +6 -3
  45. data/lib/chewy/stash.rb +3 -3
  46. data/lib/chewy/strategy/atomic_no_refresh.rb +18 -0
  47. data/lib/chewy/strategy/base.rb +10 -0
  48. data/lib/chewy/strategy/delayed_sidekiq/scheduler.rb +168 -0
  49. data/lib/chewy/strategy/delayed_sidekiq/worker.rb +76 -0
  50. data/lib/chewy/strategy/delayed_sidekiq.rb +30 -0
  51. data/lib/chewy/strategy/lazy_sidekiq.rb +64 -0
  52. data/lib/chewy/strategy.rb +3 -0
  53. data/lib/chewy/version.rb +1 -1
  54. data/lib/chewy.rb +5 -8
  55. data/lib/tasks/chewy.rake +17 -1
  56. data/migration_guide.md +1 -1
  57. data/spec/chewy/config_spec.rb +2 -2
  58. data/spec/chewy/elastic_client_spec.rb +26 -0
  59. data/spec/chewy/fields/base_spec.rb +1 -0
  60. data/spec/chewy/index/actions_spec.rb +4 -4
  61. data/spec/chewy/index/adapter/active_record_spec.rb +62 -0
  62. data/spec/chewy/index/import/bulk_builder_spec.rb +7 -3
  63. data/spec/chewy/index/import_spec.rb +16 -3
  64. data/spec/chewy/index/observe/active_record_methods_spec.rb +68 -0
  65. data/spec/chewy/index/observe/callback_spec.rb +139 -0
  66. data/spec/chewy/index/observe_spec.rb +27 -0
  67. data/spec/chewy/journal_spec.rb +13 -49
  68. data/spec/chewy/minitest/helpers_spec.rb +3 -3
  69. data/spec/chewy/minitest/search_index_receiver_spec.rb +6 -4
  70. data/spec/chewy/rake_helper_spec.rb +155 -4
  71. data/spec/chewy/rspec/helpers_spec.rb +1 -1
  72. data/spec/chewy/search/pagination/kaminari_examples.rb +1 -1
  73. data/spec/chewy/search/pagination/kaminari_spec.rb +1 -1
  74. data/spec/chewy/search/parameters/collapse_spec.rb +5 -0
  75. data/spec/chewy/search/parameters/knn_spec.rb +5 -0
  76. data/spec/chewy/search/request_spec.rb +37 -0
  77. data/spec/chewy/search_spec.rb +9 -0
  78. data/spec/chewy/strategy/active_job_spec.rb +8 -8
  79. data/spec/chewy/strategy/atomic_no_refresh_spec.rb +60 -0
  80. data/spec/chewy/strategy/delayed_sidekiq_spec.rb +208 -0
  81. data/spec/chewy/strategy/lazy_sidekiq_spec.rb +214 -0
  82. data/spec/chewy/strategy/sidekiq_spec.rb +4 -4
  83. data/spec/chewy_spec.rb +7 -4
  84. data/spec/spec_helper.rb +1 -1
  85. metadata +32 -253
  86. data/gemfiles/rails.6.0.activerecord.gemfile +0 -11
data/lib/chewy/journal.rb CHANGED
@@ -16,14 +16,17 @@ module Chewy
16
16
  # specified indexes.
17
17
  #
18
18
  # @param since_time [Time, DateTime] timestamp from which changes will be applied
19
- # @param retries [Integer] maximum number of attempts to make journal empty, 10 by default
19
+ # @param fetch_limit [Int] amount of entries to be fetched on each cycle
20
20
  # @return [Integer] the amount of journal entries found
21
- def apply(since_time, retries: 10, **import_options)
21
+ def apply(since_time, fetch_limit: 10, **import_options)
22
22
  stage = 1
23
23
  since_time -= 1
24
24
  count = 0
25
- while stage <= retries
26
- entries = Chewy::Stash::Journal.entries(since_time, only: @only).to_a.presence or break
25
+
26
+ total_count = entries(since_time, fetch_limit).total_count
27
+
28
+ while count < total_count
29
+ entries = entries(since_time, fetch_limit).to_a.presence or break
27
30
  count += entries.size
28
31
  groups = reference_groups(entries)
29
32
  ActiveSupport::Notifications.instrument 'apply_journal.chewy', stage: stage, groups: groups
@@ -40,12 +43,20 @@ module Chewy
40
43
  #
41
44
  # @param until_time [Time, DateTime] time to clean up until it
42
45
  # @return [Hash] delete_by_query ES API call result
43
- def clean(until_time = nil)
44
- Chewy::Stash::Journal.clean(until_time, only: @only)
46
+ def clean(until_time = nil, delete_by_query_options: {})
47
+ Chewy::Stash::Journal.clean(
48
+ until_time,
49
+ only: @only,
50
+ delete_by_query_options: delete_by_query_options.merge(refresh: false)
51
+ )
45
52
  end
46
53
 
47
54
  private
48
55
 
56
+ def entries(since_time, fetch_limit)
57
+ Chewy::Stash::Journal.entries(since_time, only: @only).order(:created_at).limit(fetch_limit)
58
+ end
59
+
49
60
  def reference_groups(entries)
50
61
  entries.group_by(&:index_name)
51
62
  .transform_keys { |index_name| Chewy.derive_name(index_name) }
@@ -24,7 +24,11 @@ module Chewy
24
24
 
25
25
  subject = payload[:type].presence || payload[:index]
26
26
  action = "#{subject} #{action} (#{event.duration.round(1)}ms)"
27
- action = color(action, GREEN, true)
27
+ action = if ActiveSupport.version >= Gem::Version.new('7.1')
28
+ color(action, GREEN, bold: true)
29
+ else
30
+ color(action, GREEN, true)
31
+ end
28
32
 
29
33
  debug(" #{action} #{description}")
30
34
  end
@@ -97,7 +97,7 @@ module Chewy
97
97
  {
98
98
  '_index' => index.index_name,
99
99
  '_type' => '_doc',
100
- '_id' => (i + 1).to_s,
100
+ '_id' => hit[:id] || (i + 1).to_s,
101
101
  '_score' => 3.14,
102
102
  '_source' => hit
103
103
  }
@@ -6,6 +6,8 @@
6
6
  # The class will capture the data from the *param on the Chewy::Index.bulk method and
7
7
  # aggregate the data for test analysis.
8
8
  class SearchIndexReceiver
9
+ MUTATION_FOR_CLASS = Struct.new(:indexes, :deletes, keyword_init: true)
10
+
9
11
  def initialize
10
12
  @mutations = {}
11
13
  end
@@ -71,6 +73,6 @@ private
71
73
  # @param index [Chewy::Index] the index to fetch.
72
74
  # @return [#indexes, #deletes] an object with a list of indexes and a list of deletes.
73
75
  def mutation_for(index)
74
- @mutations[index] ||= OpenStruct.new(indexes: [], deletes: [])
76
+ @mutations[index] ||= MUTATION_FOR_CLASS.new(indexes: [], deletes: [])
75
77
  end
76
78
  end
@@ -19,6 +19,9 @@ module Chewy
19
19
  output.puts " Applying journal to #{targets}, #{count} entries, stage #{payload[:stage]}"
20
20
  end
21
21
 
22
+ DELETE_BY_QUERY_OPTIONS = %w[WAIT_FOR_COMPLETION REQUESTS_PER_SECOND SCROLL_SIZE].freeze
23
+ FALSE_VALUES = %w[0 f false off].freeze
24
+
22
25
  class << self
23
26
  # Performs zero-downtime reindexing of all documents for the specified indexes
24
27
  #
@@ -162,7 +165,7 @@ module Chewy
162
165
 
163
166
  subscribed_task_stats(output) do
164
167
  output.puts "Applying journal entries created after #{time}"
165
- count = Chewy::Journal.new(indexes_from(only: only, except: except)).apply(time)
168
+ count = Chewy::Journal.new(journal_indexes_from(only: only, except: except)).apply(time)
166
169
  output.puts 'No journal entries were created after the specified time' if count.zero?
167
170
  end
168
171
  end
@@ -181,12 +184,29 @@ module Chewy
181
184
  # @param except [Array<Chewy::Index, String>, Chewy::Index, String] indexes to exclude from processing
182
185
  # @param output [IO] output io for logging
183
186
  # @return [Array<Chewy::Index>] indexes that were actually updated
184
- def journal_clean(time: nil, only: nil, except: nil, output: $stdout)
187
+ def journal_clean(time: nil, only: nil, except: nil, delete_by_query_options: {}, output: $stdout)
185
188
  subscribed_task_stats(output) do
186
189
  output.puts "Cleaning journal entries created before #{time}" if time
187
- response = Chewy::Journal.new(indexes_from(only: only, except: except)).clean(time)
188
- count = response['deleted'] || response['_indices']['_all']['deleted']
189
- output.puts "Cleaned up #{count} journal entries"
190
+ response = Chewy::Journal.new(journal_indexes_from(only: only, except: except)).clean(time, delete_by_query_options: delete_by_query_options)
191
+ if response.key?('task')
192
+ output.puts "Task to cleanup the journal has been created, #{response['task']}"
193
+ else
194
+ count = response['deleted'] || response['_indices']['_all']['deleted']
195
+ output.puts "Cleaned up #{count} journal entries"
196
+ end
197
+ end
198
+ end
199
+
200
+ # Creates journal index.
201
+ #
202
+ # @example
203
+ # Chewy::RakeHelper.journal_create # creates journal
204
+ #
205
+ # @param output [IO] output io for logging
206
+ # @return Chewy::Index Returns instance of chewy index
207
+ def journal_create(output: $stdout)
208
+ subscribed_task_stats(output) do
209
+ Chewy::Stash::Journal.create!
190
210
  end
191
211
  end
192
212
 
@@ -228,6 +248,44 @@ module Chewy
228
248
  end
229
249
  end
230
250
 
251
+ # Reads options that are required to run journal cleanup asynchronously from ENV hash
252
+ # @see https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-delete-by-query.html
253
+ #
254
+ # @example
255
+ # Chewy::RakeHelper.delete_by_query_options_from_env({'WAIT_FOR_COMPLETION' => 'false','REQUESTS_PER_SECOND' => '10','SCROLL_SIZE' => '5000'})
256
+ # # => { wait_for_completion: false, requests_per_second: 10.0, scroll_size: 5000 }
257
+ #
258
+ def delete_by_query_options_from_env(env)
259
+ env
260
+ .slice(*DELETE_BY_QUERY_OPTIONS)
261
+ .transform_keys { |k| k.downcase.to_sym }
262
+ .to_h do |key, value|
263
+ case key
264
+ when :wait_for_completion then [key, !FALSE_VALUES.include?(value.downcase)]
265
+ when :requests_per_second then [key, value.to_f]
266
+ when :scroll_size then [key, value.to_i]
267
+ end
268
+ end
269
+ end
270
+
271
+ def create_missing_indexes!(output: $stdout, env: ENV)
272
+ subscribed_task_stats(output) do
273
+ Chewy.eager_load!
274
+ all_indexes = Chewy::Index.descendants
275
+ all_indexes -= [Chewy::Stash::Journal] unless Chewy.configuration[:journal]
276
+ all_indexes.each do |index|
277
+ if index.exists?
278
+ output.puts "#{index.name} already exists, skipping" if env['VERBOSE']
279
+ next
280
+ end
281
+
282
+ index.create!
283
+
284
+ output.puts "#{index.name} index successfully created"
285
+ end
286
+ end
287
+ end
288
+
231
289
  def normalize_indexes(*identifiers)
232
290
  identifiers.flatten(1).map { |identifier| normalize_index(identifier) }
233
291
  end
@@ -243,11 +301,18 @@ module Chewy
243
301
  ActiveSupport::Notifications.subscribed(JOURNAL_CALLBACK.curry[output], 'apply_journal.chewy') do
244
302
  ActiveSupport::Notifications.subscribed(IMPORT_CALLBACK.curry[output], 'import_objects.chewy', &block)
245
303
  end
304
+ ensure
246
305
  output.puts "Total: #{human_duration(Time.now - start)}"
247
306
  end
248
307
 
249
308
  private
250
309
 
310
+ def journal_indexes_from(only: nil, except: nil)
311
+ return if Array.wrap(only).empty? && Array.wrap(except).empty?
312
+
313
+ indexes_from(only: only, except: except)
314
+ end
315
+
251
316
  def indexes_from(only: nil, except: nil)
252
317
  indexes = if only.present?
253
318
  normalize_indexes(Array.wrap(only))
@@ -255,11 +320,7 @@ module Chewy
255
320
  all_indexes
256
321
  end
257
322
 
258
- indexes = if except.present?
259
- indexes - normalize_indexes(Array.wrap(except))
260
- else
261
- indexes
262
- end
323
+ indexes -= normalize_indexes(Array.wrap(except)) if except.present?
263
324
 
264
325
  indexes.sort_by(&:derivable_name)
265
326
  end
@@ -282,9 +343,9 @@ module Chewy
282
343
  return if journal_exists?
283
344
 
284
345
  output.puts "############################################################\n" \
285
- "WARN: You are risking to lose some changes during the reset.\n" \
286
- " Please consider enabling journaling.\n" \
287
- " See https://github.com/toptal/chewy#journaling\n" \
346
+ "WARN: You are risking to lose some changes during the reset.\n " \
347
+ "Please consider enabling journaling.\n " \
348
+ "See https://github.com/toptal/chewy#journaling\n" \
288
349
  '############################################################'
289
350
  end
290
351
 
@@ -88,6 +88,11 @@ RSpec::Matchers.define :update_index do |index_name, options = {}| # rubocop:dis
88
88
  @only = true
89
89
  end
90
90
 
91
+ # Expect import to be called with refresh=false parameter
92
+ chain(:no_refresh) do
93
+ @no_refresh = true
94
+ end
95
+
91
96
  def supports_block_expectations?
92
97
  true
93
98
  end
@@ -100,11 +105,13 @@ RSpec::Matchers.define :update_index do |index_name, options = {}| # rubocop:dis
100
105
 
101
106
  index = Chewy.derive_name(index_name)
102
107
  if defined?(Mocha) && RSpec.configuration.mock_framework.to_s == 'RSpec::Core::MockingAdapters::Mocha'
103
- Chewy::Index::Import::BulkRequest.stubs(:new).with(index, any_parameters).returns(mock_bulk_request)
108
+ params_matcher = @no_refresh ? has_entry(refresh: false) : any_parameters
109
+ Chewy::Index::Import::BulkRequest.stubs(:new).with(index, params_matcher).returns(mock_bulk_request)
104
110
  else
105
- mocked_already = ::RSpec::Mocks.space.proxy_for(Chewy::Index::Import::BulkRequest).method_double_if_exists_for_message(:new)
111
+ mocked_already = RSpec::Mocks.space.proxy_for(Chewy::Index::Import::BulkRequest).method_double_if_exists_for_message(:new)
106
112
  allow(Chewy::Index::Import::BulkRequest).to receive(:new).and_call_original unless mocked_already
107
- allow(Chewy::Index::Import::BulkRequest).to receive(:new).with(index, any_args).and_return(mock_bulk_request)
113
+ params_matcher = @no_refresh ? hash_including(refresh: false) : any_args
114
+ allow(Chewy::Index::Import::BulkRequest).to receive(:new).with(index, params_matcher).and_return(mock_bulk_request)
108
115
  end
109
116
 
110
117
  Chewy.strategy(options[:strategy] || :atomic) { block.call }
@@ -146,7 +153,7 @@ RSpec::Matchers.define :update_index do |index_name, options = {}| # rubocop:dis
146
153
  output = ''
147
154
 
148
155
  if mock_bulk_request.updates.none?
149
- output << "Expected index `#{index_name}` to be updated, but it was not\n"
156
+ output << "Expected index `#{index_name}` to be updated#{' with no refresh' if @no_refresh}, but it was not\n"
150
157
  elsif @missed_reindex.present? || @missed_delete.present?
151
158
  message = "Expected index `#{index_name}` "
152
159
  message << [
@@ -213,7 +220,7 @@ RSpec::Matchers.define :update_index do |index_name, options = {}| # rubocop:dis
213
220
  expected_count = options[:times] || options[:count]
214
221
  expected_attributes = (options[:with] || options[:attributes] || {}).deep_symbolize_keys
215
222
 
216
- args.flatten.map do |document|
223
+ args.flatten.to_h do |document|
217
224
  id = document.respond_to?(:id) ? document.id.to_s : document.to_s
218
225
  [id, {
219
226
  document: document,
@@ -222,7 +229,7 @@ RSpec::Matchers.define :update_index do |index_name, options = {}| # rubocop:dis
222
229
  real_count: 0,
223
230
  real_attributes: {}
224
231
  }]
225
- end.to_h
232
+ end
226
233
  end
227
234
 
228
235
  def compare_attributes(expected, real)
@@ -5,7 +5,7 @@ module Chewy
5
5
  attr_reader :major, :minor, :patch
6
6
 
7
7
  def initialize(version)
8
- @major, @minor, @patch = *(version.to_s.split('.', 3) + [0] * 3).first(3).map(&:to_i)
8
+ @major, @minor, @patch = *(version.to_s.split('.', 3) + ([0] * 3)).first(3).map(&:to_i)
9
9
  end
10
10
 
11
11
  def to_s
@@ -0,0 +1,16 @@
1
+ require 'chewy/search/parameters/storage'
2
+
3
+ module Chewy
4
+ module Search
5
+ class Parameters
6
+ # Just a standard hash storage. Nothing to see here.
7
+ #
8
+ # @see Chewy::Search::Parameters::HashStorage
9
+ # @see Chewy::Search::Request#collapse
10
+ # @see https://www.elastic.co/guide/en/elasticsearch/reference/current/collapse-search-results.html
11
+ class Collapse < Storage
12
+ include HashStorage
13
+ end
14
+ end
15
+ end
16
+ end
@@ -17,7 +17,7 @@ module Chewy
17
17
  # @param other [Chewy::Search::Parameters::Storage] any storage instance
18
18
  # @return [true, false] the result of comparison
19
19
  def ==(other)
20
- super || other.class == self.class && other.render == render
20
+ super || (other.class == self.class && other.render == render)
21
21
  end
22
22
 
23
23
  # Just adds indices to indices.
@@ -0,0 +1,16 @@
1
+ require 'chewy/search/parameters/storage'
2
+
3
+ module Chewy
4
+ module Search
5
+ class Parameters
6
+ # Just a standard hash storage. Nothing to see here.
7
+ #
8
+ # @see Chewy::Search::Parameters::HashStorage
9
+ # @see Chewy::Search::Request#knn
10
+ # @see https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html
11
+ class Knn < Storage
12
+ include HashStorage
13
+ end
14
+ end
15
+ end
16
+ end
@@ -35,7 +35,7 @@ module Chewy
35
35
  # @param other [Chewy::Search::Parameters::Storage] any storage instance
36
36
  # @return [true, false] the result of comparision
37
37
  def ==(other)
38
- super || other.class == self.class && other.value == value
38
+ super || (other.class == self.class && other.value == value)
39
39
  end
40
40
 
41
41
  # Replaces current value with normalized provided one. Doesn't
@@ -1,5 +1,5 @@
1
- Dir.glob(File.join(File.dirname(__FILE__), 'parameters', 'concerns', '*.rb')).sort.each { |f| require f }
2
- Dir.glob(File.join(File.dirname(__FILE__), 'parameters', '*.rb')).sort.each { |f| require f }
1
+ Dir.glob(File.join(File.dirname(__FILE__), 'parameters', 'concerns', '*.rb')).each { |f| require f }
2
+ Dir.glob(File.join(File.dirname(__FILE__), 'parameters', '*.rb')).each { |f| require f }
3
3
 
4
4
  module Chewy
5
5
  module Search
@@ -53,7 +53,7 @@ module Chewy
53
53
  # @param other [Object] any object
54
54
  # @return [true, false]
55
55
  def ==(other)
56
- super || other.is_a?(self.class) && compare_storages(other)
56
+ super || (other.is_a?(self.class) && compare_storages(other))
57
57
  end
58
58
 
59
59
  # Clones the specified storage, performs the operation
@@ -20,11 +20,11 @@ module Chewy
20
20
  UNDEFINED = Class.new.freeze
21
21
  EVERFIELDS = %w[_index _type _id _parent _routing].freeze
22
22
  DELEGATED_METHODS = %i[
23
- query filter post_filter order reorder docvalue_fields
23
+ query filter post_filter knn order reorder docvalue_fields
24
24
  track_scores track_total_hits request_cache explain version profile
25
25
  search_type preference limit offset terminate_after
26
26
  timeout min_score source stored_fields search_after
27
- load script_fields suggest aggs aggregations none
27
+ load script_fields suggest aggs aggregations collapse none
28
28
  indices_boost rescore highlight total total_count
29
29
  total_entries indices types delete_all count exists?
30
30
  exist? find pluck scroll_batches scroll_hits
@@ -41,7 +41,7 @@ module Chewy
41
41
  EXTRA_STORAGES = %i[aggs suggest].freeze
42
42
  # An array of storage names that are changing the returned hist collection in any way.
43
43
  WHERE_STORAGES = %i[
44
- query filter post_filter none min_score rescore indices_boost
44
+ query filter post_filter knn none min_score rescore indices_boost collapse
45
45
  ].freeze
46
46
 
47
47
  delegate :hits, :wrappers, :objects, :records, :documents,
@@ -509,7 +509,29 @@ module Chewy
509
509
  # @see https://www.elastic.co/guide/en/elasticsearch/reference/current/multi-index.html#multi-index
510
510
  # @param value [true, false, nil]
511
511
  # @return [Chewy::Search::Request]
512
- %i[request_cache search_type preference timeout limit offset terminate_after min_score ignore_unavailable].each do |name|
512
+ #
513
+ # @!method collapse(value)
514
+ # Replaces the value of the `collapse` request part.
515
+ #
516
+ # @example
517
+ # PlacesIndex.collapse(field: :name)
518
+ # # => <PlacesIndex::Query {..., :body=>{:collapse=>{"field"=>:name}}}>
519
+ # @see Chewy::Search::Parameters::Collapse
520
+ # @see https://www.elastic.co/guide/en/elasticsearch/reference/current/collapse-search-results.html
521
+ # @param value [Hash]
522
+ # @return [Chewy::Search::Request]
523
+ #
524
+ # @!method knn(value)
525
+ # Replaces the value of the `knn` request part.
526
+ #
527
+ # @example
528
+ # PlacesIndex.knn(field: :vector, query_vector: [4, 2], k: 5, num_candidates: 50)
529
+ # # => <PlacesIndex::Query {..., :body=>{:knn=>{"field"=>:vector, "query_vector"=>[4, 2], "k"=>5, "num_candidates"=>50}}}>
530
+ # @see Chewy::Search::Parameters::Knn
531
+ # @see https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html
532
+ # @param value [Hash]
533
+ # @return [Chewy::Search::Request]
534
+ %i[request_cache search_type preference timeout limit offset terminate_after min_score ignore_unavailable collapse knn].each do |name|
513
535
  define_method name do |value|
514
536
  modify(name) { replace!(value) }
515
537
  end
@@ -800,8 +822,8 @@ module Chewy
800
822
  # Returns a new scope containing only specified storages.
801
823
  #
802
824
  # @example
803
- # PlacesIndex.limit(10).offset(10).order(:name).except(:offset, :order)
804
- # # => <PlacesIndex::Query {..., :body=>{:size=>10}}>
825
+ # PlacesIndex.limit(10).offset(10).order(:name).only(:offset, :order)
826
+ # # => <PlacesIndex::Query {..., :body=>{:from=>10, :sort=>["name"]}}>
805
827
  # @param values [Array<String, Symbol>]
806
828
  # @return [Chewy::Search::Request] new scope
807
829
  def only(*values)
@@ -811,8 +833,8 @@ module Chewy
811
833
  # Returns a new scope containing all the storages except specified.
812
834
  #
813
835
  # @example
814
- # PlacesIndex.limit(10).offset(10).order(:name).only(:offset, :order)
815
- # # => <PlacesIndex::Query {..., :body=>{:from=>10, :sort=>["name"]}}>
836
+ # PlacesIndex.limit(10).offset(10).order(:name).except(:offset, :order)
837
+ # # => <PlacesIndex::Query {..., :body=>{:size=>10}}>
816
838
  # @param values [Array<String, Symbol>]
817
839
  # @return [Chewy::Search::Request] new scope
818
840
  def except(*values)
@@ -951,10 +973,22 @@ module Chewy
951
973
  #
952
974
  # @see https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-delete-by-query.html
953
975
  # @note The result hash is different for different API used.
954
- # @param refresh [true, false] field names
976
+ # @param refresh [true, false] Refreshes all shards involved in the delete by query
977
+ # @param wait_for_completion [true, false] wait for request completion or run it asynchronously
978
+ # and return task reference at `.tasks/task/${taskId}`.
979
+ # @param requests_per_second [Float] The throttle for this request in sub-requests per second
980
+ # @param scroll_size [Integer] Size of the scroll request that powers the operation
981
+
955
982
  # @return [Hash] the result of query execution
956
- def delete_all(refresh: true)
957
- request_body = only(WHERE_STORAGES).render.merge(refresh: refresh)
983
+ def delete_all(refresh: true, wait_for_completion: nil, requests_per_second: nil, scroll_size: nil)
984
+ request_body = only(WHERE_STORAGES).render.merge(
985
+ {
986
+ refresh: refresh,
987
+ wait_for_completion: wait_for_completion,
988
+ requests_per_second: requests_per_second,
989
+ scroll_size: scroll_size
990
+ }.compact
991
+ )
958
992
  ActiveSupport::Notifications.instrument 'delete_query.chewy', notification_payload(request: request_body) do
959
993
  request_body[:body] = {query: {match_all: {}}} if request_body[:body].empty?
960
994
  Chewy.client.delete_by_query(request_body)
data/lib/chewy/search.rb CHANGED
@@ -56,7 +56,7 @@ module Chewy
56
56
  #
57
57
  # @example
58
58
  # PlacesIndex.query(match: {name: 'Moscow'})
59
- ruby2_keywords def method_missing(name, *args, &block)
59
+ def method_missing(name, *args, &block)
60
60
  if search_class::DELEGATED_METHODS.include?(name)
61
61
  all.send(name, *args, &block)
62
62
  else
@@ -84,9 +84,12 @@ module Chewy
84
84
  def delegate_scoped(source, destination, methods)
85
85
  methods.each do |method|
86
86
  destination.class_eval do
87
- define_method method do |*args, &block|
88
- scoping { source.public_send(method, *args, &block) }
87
+ define_method method do |*args, **kwargs, &block|
88
+ scoping do
89
+ source.public_send(method, *args, **kwargs, &block)
90
+ end
89
91
  end
92
+ method
90
93
  end
91
94
  end
92
95
  end
data/lib/chewy/stash.rb CHANGED
@@ -28,12 +28,12 @@ module Chewy
28
28
  # Cleans up all the journal entries until the specified time. If nothing is
29
29
  # specified - cleans up everything.
30
30
  #
31
- # @param since_time [Time, DateTime] the time top boundary
31
+ # @param until_time [Time, DateTime] Clean everything before that date
32
32
  # @param only [Chewy::Index, Array<Chewy::Index>] indexes to clean up journal entries for
33
- def self.clean(until_time = nil, only: [])
33
+ def self.clean(until_time = nil, only: [], delete_by_query_options: {})
34
34
  scope = self.for(only)
35
35
  scope = scope.filter(range: {created_at: {lte: until_time}}) if until_time
36
- scope.delete_all
36
+ scope.delete_all(**delete_by_query_options)
37
37
  end
38
38
 
39
39
  # Selects all the journal entries for the specified indices.
@@ -0,0 +1,18 @@
1
+ module Chewy
2
+ class Strategy
3
+ # This strategy works like atomic but import objects with `refresh=false` parameter.
4
+ #
5
+ # Chewy.strategy(:atomic_no_refresh) do
6
+ # User.all.map(&:save) # Does nothing here
7
+ # Post.all.map(&:save) # And here
8
+ # # It imports all the changed users and posts right here
9
+ # # before block leaving with bulk ES API, kinda optimization
10
+ # end
11
+ #
12
+ class AtomicNoRefresh < Atomic
13
+ def leave
14
+ @stash.all? { |type, ids| type.import!(ids, refresh: false) }
15
+ end
16
+ end
17
+ end
18
+ end
@@ -22,6 +22,16 @@ module Chewy
22
22
  # strategies stack
23
23
  #
24
24
  def leave; end
25
+
26
+ # This method called when some model record is created or updated.
27
+ # Normally it will just evaluate all the Chewy callbacks and pass results
28
+ # to current strategy's update method.
29
+ # However it's possible to override it to achieve delayed evaluation of
30
+ # callbacks, e.g. using sidekiq.
31
+ #
32
+ def update_chewy_indices(object)
33
+ object.run_chewy_callbacks
34
+ end
25
35
  end
26
36
  end
27
37
  end