chewy 7.4.0 → 7.5.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a5afbccc50c9249e0a78e4f2383cef346360cef2fe9f1015d05972f4df31c26f
4
- data.tar.gz: 3f0238841652782eff513dfdaa4916e8151469db5749e95e661f9c0236c526f1
3
+ metadata.gz: 64f061f379d740c56b7af2678ec03e3c80c1bd4c072b2aa3f68bf7803d4bc0fe
4
+ data.tar.gz: 6226557f9cec700d91473f36bb8623ff5bbf838211463188ac0f83eaf0ab227d
5
5
  SHA512:
6
- metadata.gz: e46394c42fc5cadabd70e5917a795bc071667c4bdf69a2b0d1d1a53c1db547a9b3221839ca8463c738030192bf135998a2de5c3652c87b6523cd08f7e69d80ad
7
- data.tar.gz: 0bbf17766315cd0bf21bd9ec5e402584212515094cd086a003c08e23594083e2ac5b44758f27b1750b82f284260c0eb8e4cb8d12e32357cf770efc917da6595e
6
+ metadata.gz: 8cb278cc5fa838a97e0c9fb94c90ea08d4a84effb041a809f850a267514c61ce78f1a919a8a0fdc5119ca968e217fe9b0aa38c307f9589f8413501f690efb314
7
+ data.tar.gz: 50ad4271e4948b22b626202843be7107fcc720fd3da490c14a372ca018ab0e35c0e151d5dc5b55f23b7c382a0d19cf72f7cc63e40a2dc1fc0ca286c1de717941
data/.rubocop.yml CHANGED
@@ -59,3 +59,6 @@ Metrics/ModuleLength:
59
59
  Exclude:
60
60
  - 'lib/chewy/rake_helper.rb'
61
61
  - '**/*_spec.rb'
62
+
63
+ Style/ArgumentsForwarding:
64
+ Enabled: false
data/CHANGELOG.md CHANGED
@@ -8,6 +8,23 @@
8
8
 
9
9
  ### Bugs Fixed
10
10
 
11
+ ## 7.5.1 (2024-01-30)
12
+
13
+ ### New Features
14
+
15
+ * [#925](https://github.com/toptal/chewy/pull/925): Add configuration option for default scope cleanup behavior. ([@barthez][])
16
+
17
+ ### Changes
18
+
19
+ ### Bugs Fixed
20
+
21
+ ## 7.5.0 (2024-01-15)
22
+
23
+ ### New Features
24
+
25
+ * [#894](https://github.com/toptal/chewy/pull/894): Way of cleaning redis from artifacts left by `delayed_sidekiq` strategy which could potentially cause flaky tests. ([@Drowze](https://github.com/Drowze))
26
+ * [#919](https://github.com/toptal/chewy/pull/919): Add pre-request filter ([@konalegi][https://github.com/konalegi])
27
+
11
28
  ## 7.4.0 (2023-12-13)
12
29
 
13
30
  ### New Features
@@ -18,7 +35,7 @@
18
35
 
19
36
  ### Bugs Fixed
20
37
 
21
- ## 7.3.5 (2023-12-13)
38
+ ## 7.3.6 (2023-12-13)
22
39
 
23
40
  ### New Features
24
41
 
data/README.md CHANGED
@@ -848,6 +848,12 @@ Explicit call of the reindex using `:delayed_sidekiq` strategy with `:update_fie
848
848
  CitiesIndex.import([1, 2, 3], update_fields: [:name], strategy: :delayed_sidekiq)
849
849
  ```
850
850
 
851
+ While running tests with delayed_sidekiq strategy and Sidekiq is using a real redis instance that is NOT cleaned up in between tests (via e.g. `Sidekiq.redis(&:flushdb)`), you'll want to cleanup some redis keys in between tests to avoid state leaking and flaky tests. Chewy provides a convenience method for that:
852
+ ```ruby
853
+ # it might be a good idea to also add to your testing setup, e.g.: a rspec `before` hook
854
+ Chewy::Strategy::DelayedSidekiq.clear_timechunks!
855
+ ```
856
+
851
857
  #### `:active_job`
852
858
 
853
859
  This does the same thing as `:atomic`, but using ActiveJob. This will inherit the ActiveJob configuration settings including the `active_job.queue_adapter` setting for the environment. Patch `Chewy::Strategy::ActiveJob::Worker` for index updates improving.
@@ -1275,6 +1281,41 @@ If you use `DatabaseCleaner` in your tests with [the `transaction` strategy](htt
1275
1281
  Chewy.use_after_commit_callbacks = !Rails.env.test?
1276
1282
  ```
1277
1283
 
1284
+ ### Pre-request Filter
1285
+
1286
+ Should you need to inspect the query prior to it being dispatched to ElasticSearch during any queries, you can use the `before_es_request_filter`. `before_es_request_filter` is a callable object, as demonstrated below:
1287
+
1288
+ ```ruby
1289
+ Chewy.before_es_request_filter = -> (method_name, args, kw_args) { ... }
1290
+ ```
1291
+
1292
+ While using the `before_es_request_filter`, please consider the following:
1293
+
1294
+ * `before_es_request_filter` acts as a simple proxy before any request made via the `ElasticSearch::Client`. The arguments passed to this filter include:
1295
+ * `method_name` - The name of the method being called. Examples are search, count, bulk and etc.
1296
+ * `args` and `kw_args` - These are the positional arguments provided in the method call.
1297
+ * The operation is synchronous, so avoid executing any heavy or time-consuming operations within the filter to prevent performance degradation.
1298
+ * The return value of the proc is disregarded. This filter is intended for inspection or modification of the query rather than generating a response.
1299
+ * Any exception raised inside the callback will propagate upward and halt the execution of the query. It is essential to handle potential errors adequately to ensure the stability of your search functionality.
1300
+
1301
+ ### Import scope clean-up behavior
1302
+
1303
+ Whenever you set the `import_scope` for the index, in the case of ActiveRecord,
1304
+ options for order, offset and limit will be removed. You can set the behavior of
1305
+ chewy, before the clean-up itself.
1306
+
1307
+ The default behavior is a warning sent to the Chewy logger (`:warn`). Another more
1308
+ restrictive option is raising an exception (`:raise`). Both options have a
1309
+ negative impact on performance since verifying whether the code uses any of
1310
+ these options requires building AREL query.
1311
+
1312
+ To avoid the loading time impact, you can ignore the check (`:ignore`) before
1313
+ the clean-up.
1314
+
1315
+ ```
1316
+ Chewy.import_scope_cleanup_behavior = :ignore
1317
+ ```
1318
+
1278
1319
  ## Contributing
1279
1320
 
1280
1321
  1. Fork it (http://github.com/toptal/chewy/fork)
@@ -6,7 +6,7 @@ gem 'rake'
6
6
  gem 'rspec', '>= 3.7.0'
7
7
  gem 'rspec-collection_matchers'
8
8
  gem 'rspec-its'
9
- gem 'rubocop', '1.48'
9
+ gem 'rubocop', '1.60.1'
10
10
  gem 'sqlite3'
11
11
  gem 'timecop'
12
12
  gem 'unparser'
data/lib/chewy/config.rb CHANGED
@@ -38,7 +38,12 @@ module Chewy
38
38
  # for type mappings like `_all`.
39
39
  :default_root_options,
40
40
  # Default field type for any field in any Chewy type. Defaults to 'text'.
41
- :default_field_type
41
+ :default_field_type,
42
+ # Callback called on each search request to be done into ES
43
+ :before_es_request_filter,
44
+ # Behavior when import scope for index includes order, offset or limit.
45
+ # Can be :ignore, :warn, :raise. Defaults to :warn
46
+ :import_scope_cleanup_behavior
42
47
 
43
48
  attr_reader :transport_logger, :transport_tracer,
44
49
  # Chewy search request DSL base class, used by every index.
@@ -60,6 +65,7 @@ module Chewy
60
65
  @indices_path = 'app/chewy'
61
66
  @default_root_options = {}
62
67
  @default_field_type = 'text'.freeze
68
+ @import_scope_cleanup_behavior = :warn
63
69
  @search_class = build_search_class(Chewy::Search::Request)
64
70
  end
65
71
 
@@ -0,0 +1,31 @@
1
+ module Chewy
2
+ # Replacement for Chewy.client
3
+ class ElasticClient
4
+ def self.build_es_client(configuration = Chewy.configuration)
5
+ client_configuration = configuration.deep_dup
6
+ client_configuration.delete(:prefix) # used by Chewy, not relevant to Elasticsearch::Client
7
+ block = client_configuration[:transport_options].try(:delete, :proc)
8
+ ::Elasticsearch::Client.new(client_configuration, &block)
9
+ end
10
+
11
+ def initialize(elastic_client = self.class.build_es_client)
12
+ @elastic_client = elastic_client
13
+ end
14
+
15
+ private
16
+
17
+ def method_missing(name, *args, **kwargs, &block)
18
+ inspect_payload(name, args, kwargs)
19
+
20
+ @elastic_client.__send__(name, *args, **kwargs, &block)
21
+ end
22
+
23
+ def respond_to_missing?(name, _include_private = false)
24
+ @elastic_client.respond_to?(name) || super
25
+ end
26
+
27
+ def inspect_payload(name, args, kwargs)
28
+ Chewy.config.before_es_request_filter&.call(name, args, kwargs)
29
+ end
30
+ end
31
+ end
data/lib/chewy/errors.rb CHANGED
@@ -7,7 +7,7 @@ module Chewy
7
7
 
8
8
  class UndefinedUpdateStrategy < Error
9
9
  def initialize(_type)
10
- super <<-MESSAGE
10
+ super(<<-MESSAGE)
11
11
  Index update strategy is undefined for current context.
12
12
  Please wrap your code with `Chewy.strategy(:strategy_name) block.`
13
13
  MESSAGE
@@ -27,7 +27,7 @@ module Chewy
27
27
  message << " on #{documents.count} documents: #{documents}\n"
28
28
  end
29
29
  end
30
- super message
30
+ super(message)
31
31
  end
32
32
  end
33
33
 
@@ -36,4 +36,7 @@ module Chewy
36
36
  super("`#{join_field_type}` set for the join field `#{join_field_name}` is not on the :relations list (#{relations})")
37
37
  end
38
38
  end
39
+
40
+ class ImportScopeCleanupError < Error
41
+ end
39
42
  end
@@ -13,9 +13,19 @@ module Chewy
13
13
  private
14
14
 
15
15
  def cleanup_default_scope!
16
- if Chewy.logger && (@default_scope.arel.orders.present? ||
16
+ behavior = Chewy.config.import_scope_cleanup_behavior
17
+
18
+ if behavior != :ignore && (@default_scope.arel.orders.present? ||
17
19
  @default_scope.arel.limit.present? || @default_scope.arel.offset.present?)
18
- Chewy.logger.warn('Default type scope order, limit and offset are ignored and will be nullified')
20
+ if behavior == :warn && Chewy.logger
21
+ gem_dir = File.realpath('../..', __dir__)
22
+ source = caller.grep_v(Regexp.new(gem_dir)).first
23
+ Chewy.logger.warn(
24
+ "Default type scope order, limit and offset are ignored and will be nullified (called from: #{source})"
25
+ )
26
+ elsif behavior == :raise
27
+ raise ImportScopeCleanupError, 'Default type scope order, limit and offset are ignored and will be nullified'
28
+ end
19
29
  end
20
30
 
21
31
  @default_scope = @default_scope.reorder(nil).limit(nil).offset(nil)
@@ -320,11 +320,7 @@ module Chewy
320
320
  all_indexes
321
321
  end
322
322
 
323
- indexes = if except.present?
324
- indexes - normalize_indexes(Array.wrap(except))
325
- else
326
- indexes
327
- end
323
+ indexes -= normalize_indexes(Array.wrap(except)) if except.present?
328
324
 
329
325
  indexes.sort_by(&:derivable_name)
330
326
  end
@@ -18,6 +18,7 @@ module Chewy
18
18
  DEFAULT_MARGIN = 2
19
19
  DEFAULT_QUEUE = 'chewy'
20
20
  KEY_PREFIX = 'chewy:delayed_sidekiq'
21
+ ALL_SETS_KEY = "#{KEY_PREFIX}:all_sets".freeze
21
22
  FALLBACK_FIELDS = 'all'
22
23
  FIELDS_IDS_SEPARATOR = ';'
23
24
  IDS_SEPARATOR = ','
@@ -68,8 +69,10 @@ module Chewy
68
69
  ::Sidekiq.redis do |redis|
69
70
  # warning: Redis#sadd will always return an Integer in Redis 5.0.0. Use Redis#sadd? instead
70
71
  if redis.respond_to?(:sadd?)
72
+ redis.sadd?(ALL_SETS_KEY, timechunks_key)
71
73
  redis.sadd?(timechunk_key, serialize_data)
72
74
  else
75
+ redis.sadd(ALL_SETS_KEY, timechunks_key)
73
76
  redis.sadd(timechunk_key, serialize_data)
74
77
  end
75
78
 
@@ -5,6 +5,19 @@ module Chewy
5
5
  class DelayedSidekiq < Sidekiq
6
6
  require_relative 'delayed_sidekiq/scheduler'
7
7
 
8
+ # cleanup the redis sets used internally. Useful mainly in tests to avoid
9
+ # leak and potential flaky tests.
10
+ def self.clear_timechunks!
11
+ ::Sidekiq.redis do |redis|
12
+ timechunk_sets = redis.smembers(Chewy::Strategy::DelayedSidekiq::Scheduler::ALL_SETS_KEY)
13
+ break if timechunk_sets.empty?
14
+
15
+ redis.pipelined do |pipeline|
16
+ timechunk_sets.each { |set| pipeline.del(set) }
17
+ end
18
+ end
19
+ end
20
+
8
21
  def leave
9
22
  @stash.each do |type, ids|
10
23
  next if ids.empty?
data/lib/chewy/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Chewy
2
- VERSION = '7.4.0'.freeze
2
+ VERSION = '7.5.1'.freeze
3
3
  end
data/lib/chewy.rb CHANGED
@@ -49,6 +49,7 @@ require 'chewy/fields/base'
49
49
  require 'chewy/fields/root'
50
50
  require 'chewy/journal'
51
51
  require 'chewy/railtie' if defined?(Rails::Railtie)
52
+ require 'chewy/elastic_client'
52
53
 
53
54
  ActiveSupport.on_load(:active_record) do
54
55
  include Chewy::Index::Observe::ActiveRecordMethods
@@ -97,12 +98,7 @@ module Chewy
97
98
  # Main elasticsearch-ruby client instance
98
99
  #
99
100
  def client
100
- Chewy.current[:chewy_client] ||= begin
101
- client_configuration = configuration.deep_dup
102
- client_configuration.delete(:prefix) # used by Chewy, not relevant to Elasticsearch::Client
103
- block = client_configuration[:transport_options].try(:delete, :proc)
104
- ::Elasticsearch::Client.new(client_configuration, &block)
105
- end
101
+ Chewy.current[:chewy_client] ||= Chewy::ElasticClient.new
106
102
  end
107
103
 
108
104
  # Sends wait_for_status request to ElasticSearch with status
@@ -0,0 +1,26 @@
1
+ require 'spec_helper'
2
+
3
+ describe Chewy::ElasticClient do
4
+ describe 'payload inspection' do
5
+ let(:filter) { instance_double('Proc') }
6
+ let!(:filter_previous_value) { Chewy.before_es_request_filter }
7
+
8
+ before do
9
+ Chewy.massacre
10
+ stub_index(:products) do
11
+ field :id, type: :integer
12
+ end
13
+ ProductsIndex.create
14
+ Chewy.before_es_request_filter = filter
15
+ end
16
+
17
+ after do
18
+ Chewy.before_es_request_filter = filter_previous_value
19
+ end
20
+
21
+ it 'call filter with the request body' do
22
+ expect(filter).to receive(:call).with(:search, [{body: {size: 0}, index: ['products']}], {})
23
+ Chewy.client.search({index: ['products'], body: {size: 0}}).to_a
24
+ end
25
+ end
26
+ end
@@ -35,6 +35,68 @@ describe Chewy::Index::Adapter::ActiveRecord, :active_record do
35
35
  specify { expect(described_class.new(City.where(rating: 10)).default_scope).to eq(City.where(rating: 10)) }
36
36
  end
37
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
+
38
100
  describe '#type_name' do
39
101
  specify { expect(described_class.new(City).type_name).to eq('city') }
40
102
  specify { expect(described_class.new(City.order(:id)).type_name).to eq('city') }
@@ -186,5 +186,17 @@ if defined?(Sidekiq)
186
186
  end
187
187
  end
188
188
  end
189
+
190
+ describe '#clear_delayed_sidekiq_timechunks test helper' do
191
+ it 'clears redis from the timechunk sorted sets to avoid leak between tests' do
192
+ timechunks_set = -> { Sidekiq.redis { |redis| redis.zrange('chewy:delayed_sidekiq:CitiesIndex:timechunks', 0, -1) } }
193
+
194
+ expect { CitiesIndex.import!([1], strategy: :delayed_sidekiq) }
195
+ .to change { timechunks_set.call.size }.by(1)
196
+
197
+ expect { Chewy::Strategy::DelayedSidekiq.clear_timechunks! }
198
+ .to change { timechunks_set.call.size }.to(0)
199
+ end
200
+ end
189
201
  end
190
202
  end
data/spec/chewy_spec.rb CHANGED
@@ -57,18 +57,21 @@ describe Chewy do
57
57
 
58
58
  before do
59
59
  Chewy.current[:chewy_client] = nil
60
- allow(Chewy).to receive_messages(configuration: {transport_options: {proc: faraday_block}})
60
+ end
61
+
62
+ specify do
63
+ expect(Chewy).to receive_messages(configuration: {transport_options: {proc: faraday_block}})
61
64
 
62
- allow(Elasticsearch::Client).to receive(:new).with(expected_client_config) do |*_args, &passed_block|
65
+ expect(Elasticsearch::Client).to receive(:new).with(expected_client_config) do |*_args, &passed_block|
63
66
  # RSpec's `with(..., &block)` was used previously, but doesn't actually do
64
67
  # any verification of the passed block (even of its presence).
65
68
  expect(passed_block.source_location).to eq(faraday_block.source_location)
66
69
 
67
70
  mock_client
68
71
  end
69
- end
70
72
 
71
- its(:client) { is_expected.to eq(mock_client) }
73
+ expect(Chewy.client).to be_a(Chewy::ElasticClient)
74
+ end
72
75
 
73
76
  after { Chewy.current[:chewy_client] = initial_client }
74
77
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: chewy
3
3
  version: !ruby/object:Gem::Version
4
- version: 7.4.0
4
+ version: 7.5.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Toptal, LLC
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2023-12-13 00:00:00.000000000 Z
12
+ date: 2024-01-30 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport
@@ -95,6 +95,7 @@ files:
95
95
  - gemfiles/rails.7.1.activerecord.gemfile
96
96
  - lib/chewy.rb
97
97
  - lib/chewy/config.rb
98
+ - lib/chewy/elastic_client.rb
98
99
  - lib/chewy/errors.rb
99
100
  - lib/chewy/fields/base.rb
100
101
  - lib/chewy/fields/root.rb
@@ -204,6 +205,7 @@ files:
204
205
  - lib/tasks/chewy.rake
205
206
  - migration_guide.md
206
207
  - spec/chewy/config_spec.rb
208
+ - spec/chewy/elastic_client_spec.rb
207
209
  - spec/chewy/fields/base_spec.rb
208
210
  - spec/chewy/fields/root_spec.rb
209
211
  - spec/chewy/fields/time_fields_spec.rb
@@ -318,7 +320,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
318
320
  - !ruby/object:Gem::Version
319
321
  version: '0'
320
322
  requirements: []
321
- rubygems_version: 3.4.10
323
+ rubygems_version: 3.2.33
322
324
  signing_key:
323
325
  specification_version: 4
324
326
  summary: Elasticsearch ODM client wrapper