chewy 7.2.1 → 7.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (108) 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 +28 -26
  5. data/.rubocop.yml +4 -1
  6. data/CHANGELOG.md +196 -0
  7. data/Gemfile +4 -3
  8. data/README.md +203 -20
  9. data/chewy.gemspec +4 -18
  10. data/gemfiles/base.gemfile +12 -0
  11. data/gemfiles/rails.6.1.activerecord.gemfile +2 -1
  12. data/gemfiles/{rails.5.2.activerecord.gemfile → rails.7.0.activerecord.gemfile} +6 -3
  13. data/gemfiles/{rails.6.0.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 +11 -2
  17. data/lib/chewy/fields/base.rb +69 -13
  18. data/lib/chewy/fields/root.rb +2 -10
  19. data/lib/chewy/index/actions.rb +11 -16
  20. data/lib/chewy/index/adapter/active_record.rb +18 -3
  21. data/lib/chewy/index/adapter/object.rb +0 -10
  22. data/lib/chewy/index/adapter/orm.rb +4 -14
  23. data/lib/chewy/index/crutch.rb +15 -7
  24. data/lib/chewy/index/import/bulk_builder.rb +219 -32
  25. data/lib/chewy/index/import/bulk_request.rb +1 -1
  26. data/lib/chewy/index/import/routine.rb +3 -3
  27. data/lib/chewy/index/import.rb +45 -31
  28. data/lib/chewy/index/mapping.rb +2 -2
  29. data/lib/chewy/index/observe/active_record_methods.rb +87 -0
  30. data/lib/chewy/index/observe/callback.rb +34 -0
  31. data/lib/chewy/index/observe.rb +3 -58
  32. data/lib/chewy/index/syncer.rb +1 -1
  33. data/lib/chewy/index.rb +25 -0
  34. data/lib/chewy/journal.rb +17 -6
  35. data/lib/chewy/log_subscriber.rb +5 -1
  36. data/lib/chewy/minitest/helpers.rb +77 -0
  37. data/lib/chewy/minitest/search_index_receiver.rb +3 -1
  38. data/lib/chewy/rake_helper.rb +92 -11
  39. data/lib/chewy/rspec/build_query.rb +12 -0
  40. data/lib/chewy/rspec/helpers.rb +55 -0
  41. data/lib/chewy/rspec/update_index.rb +14 -7
  42. data/lib/chewy/rspec.rb +2 -0
  43. data/lib/chewy/runtime/version.rb +1 -1
  44. data/lib/chewy/runtime.rb +1 -1
  45. data/lib/chewy/search/parameters/collapse.rb +16 -0
  46. data/lib/chewy/search/parameters/ignore_unavailable.rb +27 -0
  47. data/lib/chewy/search/parameters/indices.rb +1 -1
  48. data/lib/chewy/search/parameters/knn.rb +16 -0
  49. data/lib/chewy/search/parameters/order.rb +6 -19
  50. data/lib/chewy/search/parameters/storage.rb +1 -1
  51. data/lib/chewy/search/parameters/track_total_hits.rb +16 -0
  52. data/lib/chewy/search/parameters.rb +4 -4
  53. data/lib/chewy/search/request.rb +74 -16
  54. data/lib/chewy/search/scoping.rb +1 -1
  55. data/lib/chewy/search.rb +5 -2
  56. data/lib/chewy/stash.rb +3 -3
  57. data/lib/chewy/strategy/active_job.rb +1 -1
  58. data/lib/chewy/strategy/atomic_no_refresh.rb +18 -0
  59. data/lib/chewy/strategy/base.rb +10 -0
  60. data/lib/chewy/strategy/delayed_sidekiq/scheduler.rb +168 -0
  61. data/lib/chewy/strategy/delayed_sidekiq/worker.rb +76 -0
  62. data/lib/chewy/strategy/delayed_sidekiq.rb +30 -0
  63. data/lib/chewy/strategy/lazy_sidekiq.rb +64 -0
  64. data/lib/chewy/strategy/sidekiq.rb +1 -1
  65. data/lib/chewy/strategy.rb +3 -0
  66. data/lib/chewy/version.rb +1 -1
  67. data/lib/chewy.rb +21 -14
  68. data/lib/tasks/chewy.rake +18 -2
  69. data/migration_guide.md +1 -1
  70. data/spec/chewy/config_spec.rb +2 -2
  71. data/spec/chewy/elastic_client_spec.rb +26 -0
  72. data/spec/chewy/fields/base_spec.rb +39 -18
  73. data/spec/chewy/index/actions_spec.rb +10 -10
  74. data/spec/chewy/index/adapter/active_record_spec.rb +88 -0
  75. data/spec/chewy/index/import/bulk_builder_spec.rb +309 -1
  76. data/spec/chewy/index/import/routine_spec.rb +5 -5
  77. data/spec/chewy/index/import_spec.rb +48 -26
  78. data/spec/chewy/index/observe/active_record_methods_spec.rb +68 -0
  79. data/spec/chewy/index/observe/callback_spec.rb +139 -0
  80. data/spec/chewy/index/observe_spec.rb +27 -0
  81. data/spec/chewy/journal_spec.rb +13 -49
  82. data/spec/chewy/minitest/helpers_spec.rb +111 -1
  83. data/spec/chewy/minitest/search_index_receiver_spec.rb +6 -4
  84. data/spec/chewy/rake_helper_spec.rb +170 -0
  85. data/spec/chewy/rspec/build_query_spec.rb +34 -0
  86. data/spec/chewy/rspec/helpers_spec.rb +61 -0
  87. data/spec/chewy/search/pagination/kaminari_examples.rb +1 -1
  88. data/spec/chewy/search/pagination/kaminari_spec.rb +1 -1
  89. data/spec/chewy/search/parameters/collapse_spec.rb +5 -0
  90. data/spec/chewy/search/parameters/ignore_unavailable_spec.rb +67 -0
  91. data/spec/chewy/search/parameters/knn_spec.rb +5 -0
  92. data/spec/chewy/search/parameters/order_spec.rb +18 -11
  93. data/spec/chewy/search/parameters/track_total_hits_spec.rb +5 -0
  94. data/spec/chewy/search/parameters_spec.rb +6 -1
  95. data/spec/chewy/search/request_spec.rb +58 -9
  96. data/spec/chewy/search_spec.rb +9 -0
  97. data/spec/chewy/strategy/active_job_spec.rb +8 -8
  98. data/spec/chewy/strategy/atomic_no_refresh_spec.rb +60 -0
  99. data/spec/chewy/strategy/delayed_sidekiq_spec.rb +208 -0
  100. data/spec/chewy/strategy/lazy_sidekiq_spec.rb +214 -0
  101. data/spec/chewy/strategy/sidekiq_spec.rb +4 -4
  102. data/spec/chewy_spec.rb +10 -7
  103. data/spec/spec_helper.rb +1 -2
  104. data/spec/support/active_record.rb +8 -1
  105. metadata +45 -264
  106. data/lib/chewy/backports/deep_dup.rb +0 -46
  107. data/lib/chewy/backports/duplicable.rb +0 -91
  108. data/lib/chewy/index/import/thread_safe_progress_bar.rb +0 -40
@@ -1,9 +1,12 @@
1
1
  require 'spec_helper'
2
+ require 'rake'
2
3
 
3
4
  describe Chewy::RakeHelper, :orm do
4
5
  before { Chewy.massacre }
5
6
 
6
7
  before do
8
+ described_class.instance_variable_set(:@journal_exists, journal_exists)
9
+
7
10
  stub_model(:city)
8
11
  stub_model(:country)
9
12
 
@@ -20,6 +23,7 @@ describe Chewy::RakeHelper, :orm do
20
23
  allow(described_class).to receive(:all_indexes) { [CitiesIndex, CountriesIndex, UsersIndex] }
21
24
  end
22
25
 
26
+ let(:journal_exists) { true }
23
27
  let!(:cities) { Array.new(3) { |i| City.create!(name: "Name#{i + 1}") } }
24
28
  let!(:countries) { Array.new(2) { |i| Country.create!(name: "Name#{i + 1}") } }
25
29
  let(:journal) do
@@ -92,6 +96,22 @@ Total: \\d+s\\Z
92
96
  Total: \\d+s\\Z
93
97
  OUTPUT
94
98
  end
99
+
100
+ context 'when journal is missing' do
101
+ let(:journal_exists) { false }
102
+
103
+ specify do
104
+ output = StringIO.new
105
+ expect { described_class.reset(only: [CitiesIndex], output: output) }
106
+ .to update_index(CitiesIndex)
107
+ expect(output.string).to include(
108
+ "############################################################\n" \
109
+ "WARN: You are risking to lose some changes during the reset.\n " \
110
+ "Please consider enabling journaling.\n " \
111
+ 'See https://github.com/toptal/chewy#journaling'
112
+ )
113
+ end
114
+ end
95
115
  end
96
116
 
97
117
  describe '.upgrade' do
@@ -407,6 +427,108 @@ Total: \\d+s\\Z
407
427
  described_class.journal_clean(except: CitiesIndex, output: output)
408
428
  expect(output.string).to match(Regexp.new(<<-OUTPUT, Regexp::MULTILINE))
409
429
  \\ACleaned up 1 journal entries
430
+ Total: \\d+s\\Z
431
+ OUTPUT
432
+ end
433
+
434
+ it 'executes asynchronously' do
435
+ output = StringIO.new
436
+ expect(Chewy.client).to receive(:delete_by_query).with(
437
+ {
438
+ body: {query: {match_all: {}}},
439
+ index: ['chewy_journal'],
440
+ refresh: false,
441
+ requests_per_second: 10.0,
442
+ scroll_size: 200,
443
+ wait_for_completion: false
444
+ }
445
+ ).and_call_original
446
+ described_class.journal_clean(
447
+ output: output,
448
+ delete_by_query_options: {
449
+ wait_for_completion: false,
450
+ requests_per_second: 10.0,
451
+ scroll_size: 200
452
+ }
453
+ )
454
+
455
+ expect(output.string).to match(Regexp.new(<<-OUTPUT, Regexp::MULTILINE))
456
+ \\ATask to cleanup the journal has been created, [^\\n]*
457
+ Total: \\d+s\\Z
458
+ OUTPUT
459
+ end
460
+
461
+ context 'execute "chewy:journal:clean" rake task' do
462
+ subject(:task) { Rake.application['chewy:journal:clean'] }
463
+ before do
464
+ Rake::DefaultLoader.new.load('lib/tasks/chewy.rake')
465
+ Rake::Task.define_task(:environment)
466
+ end
467
+ it 'does not raise error' do
468
+ expect { task.invoke }.to_not raise_error
469
+ end
470
+ end
471
+ end
472
+
473
+ describe '.create_missing_indexes!' do
474
+ before do
475
+ [CountriesIndex, Chewy::Stash::Specification].map(&:create!)
476
+
477
+ # To avoid flaky issues when previous specs were run
478
+ expect(Chewy::Index).to receive(:descendants).and_return(
479
+ [
480
+ UsersIndex,
481
+ CountriesIndex,
482
+ CitiesIndex,
483
+ Chewy::Stash::Specification,
484
+ Chewy::Stash::Journal
485
+ ]
486
+ )
487
+ end
488
+
489
+ specify do
490
+ output = StringIO.new
491
+ described_class.create_missing_indexes!(output: output)
492
+ expect(CitiesIndex.exists?).to be_truthy
493
+ expect(UsersIndex.exists?).to be_truthy
494
+ expect(Chewy::Stash::Journal.exists?).to be_falsey
495
+ expect(output.string).to match(Regexp.new(<<-OUTPUT, Regexp::MULTILINE))
496
+ UsersIndex index successfully created
497
+ CitiesIndex index successfully created
498
+ Total: \\d+s\\Z
499
+ OUTPUT
500
+ end
501
+
502
+ context 'when verbose' do
503
+ specify do
504
+ output = StringIO.new
505
+ described_class.create_missing_indexes!(output: output, env: {'VERBOSE' => '1'})
506
+ expect(output.string).to match(Regexp.new(<<-OUTPUT, Regexp::MULTILINE))
507
+ UsersIndex index successfully created
508
+ CountriesIndex already exists, skipping
509
+ CitiesIndex index successfully created
510
+ Chewy::Stash::Specification already exists, skipping
511
+ Total: \\d+s\\Z
512
+ OUTPUT
513
+ end
514
+ end
515
+
516
+ context 'when journaling is enabled' do
517
+ before { Chewy.config.settings[:journal] = true }
518
+ after { Chewy.config.settings.delete(:journal) }
519
+ specify do
520
+ described_class.create_missing_indexes!(output: StringIO.new)
521
+ expect(Chewy::Stash::Journal.exists?).to be_truthy
522
+ end
523
+ end
524
+ end
525
+
526
+ describe '.journal_create' do
527
+ specify do
528
+ output = StringIO.new
529
+ described_class.journal_create(output: output)
530
+ expect(Chewy::Stash::Journal.exists?).to be_truthy
531
+ expect(output.string).to match(Regexp.new(<<-OUTPUT, Regexp::MULTILINE))
410
532
  Total: \\d+s\\Z
411
533
  OUTPUT
412
534
  end
@@ -483,4 +605,52 @@ Total: \\d+s\\Z
483
605
  end
484
606
  end
485
607
  end
608
+
609
+ describe '.delete_by_query_options_from_env' do
610
+ subject(:options) { described_class.delete_by_query_options_from_env(env) }
611
+ let(:env) do
612
+ {
613
+ 'WAIT_FOR_COMPLETION' => 'false',
614
+ 'REQUESTS_PER_SECOND' => '10',
615
+ 'SCROLL_SIZE' => '5000'
616
+ }
617
+ end
618
+
619
+ it 'parses the options' do
620
+ expect(options).to eq(
621
+ wait_for_completion: false,
622
+ requests_per_second: 10.0,
623
+ scroll_size: 5000
624
+ )
625
+ end
626
+
627
+ context 'with different boolean values' do
628
+ it 'parses the option correctly' do
629
+ %w[1 t true TRUE on ON].each do |v|
630
+ expect(described_class.delete_by_query_options_from_env({'WAIT_FOR_COMPLETION' => v}))
631
+ .to eq(wait_for_completion: true)
632
+ end
633
+
634
+ %w[0 f false FALSE off OFF].each do |v|
635
+ expect(described_class.delete_by_query_options_from_env({'WAIT_FOR_COMPLETION' => v}))
636
+ .to eq(wait_for_completion: false)
637
+ end
638
+ end
639
+ end
640
+
641
+ context 'with other env' do
642
+ let(:env) { {'SOME_ENV' => '123', 'REQUESTS_PER_SECOND' => '15'} }
643
+
644
+ it 'parses only the options' do
645
+ expect(options).to eq(requests_per_second: 15.0)
646
+ end
647
+ end
648
+ end
649
+
650
+ describe '.subscribed_task_stats' do
651
+ specify do
652
+ block_output = described_class.subscribed_task_stats(StringIO.new) { 'expected output' }
653
+ expect(block_output).to eq('expected output')
654
+ end
655
+ end
486
656
  end
@@ -0,0 +1,34 @@
1
+ require 'spec_helper'
2
+
3
+ describe :build_query do
4
+ before do
5
+ stub_model(:city)
6
+ stub_index(:cities) { index_scope City }
7
+ CitiesIndex.create
8
+ end
9
+
10
+ let(:expected_query) do
11
+ {
12
+ index: ['cities'],
13
+ body: {
14
+ query: {
15
+ match: {name: 'name'}
16
+ }
17
+ }
18
+ }
19
+ end
20
+ let(:dummy_query) { {match: {name: 'name'}} }
21
+ let(:unexpected_query) { {match: {name: 'name'}} }
22
+
23
+ context 'build expected query' do
24
+ specify do
25
+ expect(CitiesIndex.query(dummy_query)).to build_query(expected_query)
26
+ end
27
+ end
28
+
29
+ context 'not to build unexpected query' do
30
+ specify do
31
+ expect(CitiesIndex.query(dummy_query)).not_to build_query(unexpected_query)
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,61 @@
1
+ require 'spec_helper'
2
+
3
+ describe :rspec_helper do
4
+ include Chewy::Rspec::Helpers
5
+
6
+ before do
7
+ stub_model(:city)
8
+ stub_index(:cities) { index_scope City }
9
+ CitiesIndex.create
10
+ end
11
+
12
+ let(:hits) do
13
+ [
14
+ {
15
+ '_index' => 'cities',
16
+ '_type' => '_doc',
17
+ '_id' => '1',
18
+ '_score' => 3.14,
19
+ '_source' => source
20
+ }
21
+ ]
22
+ end
23
+
24
+ let(:source) { {'name' => 'some_name'} }
25
+ let(:sources) { [source] }
26
+
27
+ context :mock_elasticsearch_response do
28
+ let(:raw_response) do
29
+ {
30
+ 'took' => 4,
31
+ 'timed_out' => false,
32
+ '_shards' => {
33
+ 'total' => 1,
34
+ 'successful' => 1,
35
+ 'skipped' => 0,
36
+ 'failed' => 0
37
+ },
38
+ 'hits' => {
39
+ 'total' => {
40
+ 'value' => 1,
41
+ 'relation' => 'eq'
42
+ },
43
+ 'max_score' => 1.0,
44
+ 'hits' => hits
45
+ }
46
+ }
47
+ end
48
+
49
+ specify do
50
+ mock_elasticsearch_response(CitiesIndex, raw_response)
51
+ expect(CitiesIndex.query({}).hits).to eq(hits)
52
+ end
53
+ end
54
+
55
+ context :mock_elasticsearch_response_sources do
56
+ specify do
57
+ mock_elasticsearch_response_sources(CitiesIndex, sources)
58
+ expect(CitiesIndex.query({}).hits).to eq(hits)
59
+ end
60
+ end
61
+ end
@@ -24,7 +24,7 @@ shared_examples :kaminari do |request_base_class|
24
24
  let(:data) { Array.new(10) { |i| {id: i.next.to_s, name: "Name#{i.next}", age: 10 * i.next}.stringify_keys! } }
25
25
 
26
26
  before { ProductsIndex.import!(data.map { |h| double(h) }) }
27
- before { allow(::Kaminari.config).to receive_messages(default_per_page: 3) }
27
+ before { allow(Kaminari.config).to receive_messages(default_per_page: 3) }
28
28
 
29
29
  describe '#per, #page' do
30
30
  specify { expect(search.map { |e| e.attributes.except(*except_fields) }).to match_array(data) }
@@ -6,7 +6,7 @@ describe Chewy::Search::Pagination::Kaminari do
6
6
  let(:data) { Array.new(12) { |i| {id: i.next.to_s, name: "Name#{i.next}", age: 10 * i.next}.stringify_keys! } }
7
7
 
8
8
  before { ProductsIndex.import!(data.map { |h| double(h) }) }
9
- before { allow(::Kaminari.config).to receive_messages(default_per_page: 17) }
9
+ before { allow(Kaminari.config).to receive_messages(default_per_page: 17) }
10
10
 
11
11
  specify { expect(search.objects.class).to eq(Kaminari::PaginatableArray) }
12
12
  specify { expect(search.objects.total_count).to eq(12) }
@@ -0,0 +1,5 @@
1
+ require 'chewy/search/parameters/hash_storage_examples'
2
+
3
+ describe Chewy::Search::Parameters::Collapse do
4
+ it_behaves_like :hash_storage, :collapse
5
+ end
@@ -0,0 +1,67 @@
1
+ require 'spec_helper'
2
+
3
+ describe Chewy::Search::Parameters::IgnoreUnavailable do
4
+ subject { described_class.new(true) }
5
+
6
+ describe '#initialize' do
7
+ specify { expect(subject.value).to eq(true) }
8
+ specify { expect(described_class.new.value).to eq(nil) }
9
+ specify { expect(described_class.new(42).value).to eq(true) }
10
+ specify { expect(described_class.new(false).value).to eq(false) }
11
+ end
12
+
13
+ describe '#replace!' do
14
+ specify { expect { subject.replace!(false) }.to change { subject.value }.from(true).to(false) }
15
+ specify { expect { subject.replace!(nil) }.to change { subject.value }.from(true).to(nil) }
16
+ end
17
+
18
+ describe '#update!' do
19
+ specify { expect { subject.update!(nil) }.not_to change { subject.value }.from(true) }
20
+ specify { expect { subject.update!(false) }.to change { subject.value }.from(true).to(false) }
21
+ specify { expect { subject.update!(true) }.not_to change { subject.value }.from(true) }
22
+
23
+ context do
24
+ subject { described_class.new(false) }
25
+
26
+ specify { expect { subject.update!(nil) }.not_to change { subject.value }.from(false) }
27
+ specify { expect { subject.update!(false) }.not_to change { subject.value }.from(false) }
28
+ specify { expect { subject.update!(true) }.to change { subject.value }.from(false).to(true) }
29
+ end
30
+
31
+ context do
32
+ subject { described_class.new }
33
+
34
+ specify { expect { subject.update!(nil) }.not_to change { subject.value }.from(nil) }
35
+ specify { expect { subject.update!(false) }.to change { subject.value }.from(nil).to(false) }
36
+ specify { expect { subject.update!(true) }.to change { subject.value }.from(nil).to(true) }
37
+ end
38
+ end
39
+
40
+ describe '#merge!' do
41
+ specify { expect { subject.merge!(described_class.new) }.not_to change { subject.value }.from(true) }
42
+ specify { expect { subject.merge!(described_class.new(false)) }.to change { subject.value }.from(true).to(false) }
43
+ specify { expect { subject.merge!(described_class.new(true)) }.not_to change { subject.value }.from(true) }
44
+
45
+ context do
46
+ subject { described_class.new(false) }
47
+
48
+ specify { expect { subject.merge!(described_class.new) }.not_to change { subject.value }.from(false) }
49
+ specify { expect { subject.merge!(described_class.new(false)) }.not_to change { subject.value }.from(false) }
50
+ specify { expect { subject.merge!(described_class.new(true)) }.to change { subject.value }.from(false).to(true) }
51
+ end
52
+
53
+ context do
54
+ subject { described_class.new }
55
+
56
+ specify { expect { subject.merge!(described_class.new) }.not_to change { subject.value }.from(nil) }
57
+ specify { expect { subject.merge!(described_class.new(false)) }.to change { subject.value }.from(nil).to(false) }
58
+ specify { expect { subject.merge!(described_class.new(true)) }.to change { subject.value }.from(nil).to(true) }
59
+ end
60
+ end
61
+
62
+ describe '#render' do
63
+ specify { expect(described_class.new.render).to be_nil }
64
+ specify { expect(described_class.new(false).render).to eq(ignore_unavailable: false) }
65
+ specify { expect(subject.render).to eq(ignore_unavailable: true) }
66
+ end
67
+ end
@@ -0,0 +1,5 @@
1
+ require 'chewy/search/parameters/hash_storage_examples'
2
+
3
+ describe Chewy::Search::Parameters::Knn do
4
+ it_behaves_like :hash_storage, :knn
5
+ end
@@ -4,26 +4,29 @@ describe Chewy::Search::Parameters::Order do
4
4
  subject { described_class.new(%i[foo bar]) }
5
5
 
6
6
  describe '#initialize' do
7
- specify { expect(described_class.new.value).to eq({}) }
8
- specify { expect(described_class.new(nil).value).to eq({}) }
9
- specify { expect(described_class.new('').value).to eq({}) }
10
- specify { expect(described_class.new(42).value).to eq('42' => nil) }
11
- specify { expect(described_class.new([42, 43]).value).to eq('42' => nil, '43' => nil) }
12
- specify { expect(described_class.new(a: 1).value).to eq('a' => 1) }
13
- specify { expect(described_class.new(['', 43, {a: 1}]).value).to eq('a' => 1, '43' => nil) }
7
+ specify { expect(described_class.new.value).to eq([]) }
8
+ specify { expect(described_class.new(nil).value).to eq([]) }
9
+ specify { expect(described_class.new('').value).to eq([]) }
10
+ specify { expect(described_class.new(42).value).to eq(['42']) }
11
+ specify { expect(described_class.new([42, 43]).value).to eq(%w[42 43]) }
12
+ specify { expect(described_class.new([42, 42]).value).to eq(%w[42 42]) }
13
+ specify { expect(described_class.new([42, [43, 44]]).value).to eq(%w[42 43 44]) }
14
+ specify { expect(described_class.new(a: 1).value).to eq([{'a' => 1}]) }
15
+ specify { expect(described_class.new(['a', {a: 1}, {a: 2}]).value).to eq(['a', {'a' => 1}, {'a' => 2}]) }
16
+ specify { expect(described_class.new(['', 43, {a: 1}]).value).to eq(['43', {'a' => 1}]) }
14
17
  end
15
18
 
16
19
  describe '#replace!' do
17
20
  specify do
18
21
  expect { subject.replace!(foo: {}) }
19
22
  .to change { subject.value }
20
- .from('foo' => nil, 'bar' => nil).to('foo' => {})
23
+ .from(%w[foo bar]).to([{'foo' => {}}])
21
24
  end
22
25
 
23
26
  specify do
24
27
  expect { subject.replace!(nil) }
25
28
  .to change { subject.value }
26
- .from('foo' => nil, 'bar' => nil).to({})
29
+ .from(%w[foo bar]).to([])
27
30
  end
28
31
  end
29
32
 
@@ -31,7 +34,7 @@ describe Chewy::Search::Parameters::Order do
31
34
  specify do
32
35
  expect { subject.update!(foo: {}) }
33
36
  .to change { subject.value }
34
- .from('foo' => nil, 'bar' => nil).to('foo' => {}, 'bar' => nil)
37
+ .from(%w[foo bar]).to(['foo', 'bar', {'foo' => {}}])
35
38
  end
36
39
 
37
40
  specify { expect { subject.update!(nil) }.not_to change { subject.value } }
@@ -41,7 +44,7 @@ describe Chewy::Search::Parameters::Order do
41
44
  specify do
42
45
  expect { subject.merge!(described_class.new(foo: {})) }
43
46
  .to change { subject.value }
44
- .from('foo' => nil, 'bar' => nil).to('foo' => {}, 'bar' => nil)
47
+ .from(%w[foo bar]).to(['foo', 'bar', {'foo' => {}}])
45
48
  end
46
49
 
47
50
  specify { expect { subject.merge!(described_class.new) }.not_to change { subject.value } }
@@ -51,6 +54,7 @@ describe Chewy::Search::Parameters::Order do
51
54
  specify { expect(described_class.new.render).to be_nil }
52
55
  specify { expect(described_class.new(:foo).render).to eq(sort: ['foo']) }
53
56
  specify { expect(described_class.new([:foo, {bar: 42}, :baz]).render).to eq(sort: ['foo', {'bar' => 42}, 'baz']) }
57
+ specify { expect(described_class.new([:foo, {bar: 42}, {bar: 43}, :baz]).render).to eq(sort: ['foo', {'bar' => 42}, {'bar' => 43}, 'baz']) }
54
58
  end
55
59
 
56
60
  describe '#==' do
@@ -59,7 +63,10 @@ describe Chewy::Search::Parameters::Order do
59
63
  specify { expect(described_class.new(:foo)).not_to eq(described_class.new(:bar)) }
60
64
  specify { expect(described_class.new(%i[foo bar])).to eq(described_class.new(%i[foo bar])) }
61
65
  specify { expect(described_class.new(%i[foo bar])).not_to eq(described_class.new(%i[bar foo])) }
66
+ specify { expect(described_class.new(%i[foo foo])).not_to eq(described_class.new(%i[foo])) }
62
67
  specify { expect(described_class.new(foo: {a: 42})).to eq(described_class.new(foo: {a: 42})) }
63
68
  specify { expect(described_class.new(foo: {a: 42})).not_to eq(described_class.new(foo: {b: 42})) }
69
+ specify { expect(described_class.new(['foo', {'foo' => 42}])).not_to eq(described_class.new([{'foo' => 42}, 'foo'])) }
70
+ specify { expect(described_class.new([{'foo' => 42}, {'foo' => 43}])).not_to eq(described_class.new([{'foo' => 43}, {'foo' => 42}])) }
64
71
  end
65
72
  end
@@ -0,0 +1,5 @@
1
+ require 'chewy/search/parameters/bool_storage_examples'
2
+
3
+ describe Chewy::Search::Parameters::TrackTotalHits do
4
+ it_behaves_like :bool_storage, :track_total_hits
5
+ end
@@ -13,7 +13,7 @@ describe Chewy::Search::Parameters do
13
13
 
14
14
  specify { expect(subject.storages[:limit]).to equal(limit) }
15
15
  specify { expect(subject.storages[:limit].value).to eq(3) }
16
- specify { expect(subject.storages[:order].value).to eq('foo' => nil) }
16
+ specify { expect(subject.storages[:order].value).to eq(['foo']) }
17
17
 
18
18
  specify { expect { described_class.new(offset: limit) }.to raise_error(TypeError) }
19
19
  end
@@ -128,6 +128,11 @@ describe Chewy::Search::Parameters do
128
128
  specify { expect(subject.render).to eq(body: {}, allow_partial_search_results: true) }
129
129
  end
130
130
 
131
+ context do
132
+ subject { described_class.new(ignore_unavailable: true) }
133
+ specify { expect(subject.render).to eq(body: {}, ignore_unavailable: true) }
134
+ end
135
+
131
136
  context do
132
137
  subject { described_class.new(query: {foo: 'bar'}, filter: {moo: 'baz'}) }
133
138
  specify { expect(subject.render).to eq(body: {query: {bool: {must: {foo: 'bar'}, filter: {moo: 'baz'}}}}) }
@@ -177,7 +177,7 @@ describe Chewy::Search::Request do
177
177
  describe '#order' do
178
178
  specify { expect(subject.order(:foo).render[:body]).to include(sort: ['foo']) }
179
179
  specify { expect(subject.order(foo: 42).order(nil).render[:body]).to include(sort: ['foo' => 42]) }
180
- specify { expect(subject.order(foo: 42).order(foo: 43).render[:body]).to include(sort: ['foo' => 43]) }
180
+ specify { expect(subject.order(foo: 42).order(foo: 43).render[:body]).to include(sort: [{'foo' => 42}, {'foo' => 43}]) }
181
181
  specify { expect(subject.order(:foo).order(:bar, :baz).render[:body]).to include(sort: %w[foo bar baz]) }
182
182
  specify { expect(subject.order(nil).render[:body]).to be_blank }
183
183
  specify { expect { subject.order(:foo) }.not_to change { subject.render } }
@@ -192,7 +192,7 @@ describe Chewy::Search::Request do
192
192
  specify { expect { subject.reorder(:foo) }.not_to change { subject.render } }
193
193
  end
194
194
 
195
- %i[track_scores explain version profile].each do |name|
195
+ %i[track_scores track_total_hits explain version profile].each do |name|
196
196
  describe "##{name}" do
197
197
  specify { expect(subject.send(name).render[:body]).to include(name => true) }
198
198
  specify { expect(subject.send(name).send(name, false).render[:body]).to be_blank }
@@ -214,13 +214,18 @@ describe Chewy::Search::Request do
214
214
  specify { expect { subject.search_type('foo') }.not_to change { subject.render } }
215
215
  end
216
216
 
217
- %i[preference timeout].each do |name|
218
- describe "##{name}" do
219
- specify { expect(subject.send(name, :foo).render[:body]).to include(name => 'foo') }
220
- specify { expect(subject.send(name, :foo).send(name, :bar).render[:body]).to include(name => 'bar') }
221
- specify { expect(subject.send(name, :foo).send(name, nil).render[:body]).to be_blank }
222
- specify { expect { subject.send(name, :foo) }.not_to change { subject.render } }
223
- end
217
+ describe '#preference' do
218
+ specify { expect(subject.preference('foo').render).to include(preference: 'foo') }
219
+ specify { expect(subject.preference('foo').preference('bar').render).to include(preference: 'bar') }
220
+ specify { expect(subject.preference('foo').preference(nil).render[:preference]).to be_blank }
221
+ specify { expect { subject.preference('foo') }.not_to change { subject.render } }
222
+ end
223
+
224
+ describe '#timeout' do
225
+ specify { expect(subject.timeout(:foo).render[:body]).to include(timeout: 'foo') }
226
+ specify { expect(subject.timeout(:foo).timeout(:bar).render[:body]).to include(timeout: 'bar') }
227
+ specify { expect(subject.timeout(:foo).timeout(nil).render[:body]).to be_blank }
228
+ specify { expect { subject.timeout(:foo) }.not_to change { subject.render } }
224
229
  end
225
230
 
226
231
  describe '#source' do
@@ -309,6 +314,18 @@ describe Chewy::Search::Request do
309
314
  end
310
315
  end
311
316
 
317
+ %i[collapse knn].each do |name|
318
+ describe "##{name}" do
319
+ specify { expect(subject.send(name, foo: {bar: 42}).render[:body]).to include(name => {'foo' => {bar: 42}}) }
320
+ specify do
321
+ expect(subject.send(name, foo: {bar: 42}).send(name, moo: {baz: 43}).render[:body])
322
+ .to include(name => {'moo' => {baz: 43}})
323
+ end
324
+ specify { expect(subject.send(name, foo: {bar: 42}).send(name, nil).render[:body]).to be_blank }
325
+ specify { expect { subject.send(name, foo: {bar: 42}) }.not_to change { subject.render } }
326
+ end
327
+ end
328
+
312
329
  describe '#docvalue_fields' do
313
330
  specify { expect(subject.docvalue_fields(:foo).render[:body]).to include(docvalue_fields: ['foo']) }
314
331
  specify do
@@ -361,6 +378,13 @@ describe Chewy::Search::Request do
361
378
  specify { expect { subject.min_score(1.2) }.not_to change { subject.render } }
362
379
  end
363
380
 
381
+ describe '#ignore_unavailable' do
382
+ specify { expect(subject.ignore_unavailable(true).render).to include(ignore_unavailable: true) }
383
+ specify { expect(subject.ignore_unavailable(true).ignore_unavailable(false).render).to include(ignore_unavailable: false) }
384
+ specify { expect(subject.ignore_unavailable(true).ignore_unavailable(nil).render[:ignore_unavailable]).to be_blank }
385
+ specify { expect { subject.ignore_unavailable(true) }.not_to change { subject.render } }
386
+ end
387
+
364
388
  describe '#search_after' do
365
389
  specify { expect(subject.search_after(:foo, :bar).render[:body]).to include(search_after: %i[foo bar]) }
366
390
  specify do
@@ -795,6 +819,31 @@ describe Chewy::Search::Request do
795
819
  request: {index: ['products'], body: {query: {match: {name: 'name3'}}}, refresh: false}
796
820
  )
797
821
  end
822
+
823
+ it 'delete records asynchronously' do
824
+ outer_payload = nil
825
+ ActiveSupport::Notifications.subscribe('delete_query.chewy') do |_name, _start, _finish, _id, payload|
826
+ outer_payload = payload
827
+ end
828
+ subject.query(match: {name: 'name3'}).delete_all(
829
+ refresh: false,
830
+ wait_for_completion: false,
831
+ requests_per_second: 10.0,
832
+ scroll_size: 2000
833
+ )
834
+ expect(outer_payload).to eq(
835
+ index: ProductsIndex,
836
+ indexes: [ProductsIndex],
837
+ request: {
838
+ index: ['products'],
839
+ body: {query: {match: {name: 'name3'}}},
840
+ refresh: false,
841
+ wait_for_completion: false,
842
+ requests_per_second: 10.0,
843
+ scroll_size: 2000
844
+ }
845
+ )
846
+ end
798
847
  end
799
848
 
800
849
  describe '#response=' do
@@ -48,6 +48,10 @@ describe Chewy::Search do
48
48
  filter { match name: "Name#{index}" }
49
49
  end
50
50
 
51
+ def self.by_rating_with_kwargs(value, options:) # rubocop:disable Lint/UnusedMethodArgument
52
+ filter { match rating: value }
53
+ end
54
+
51
55
  index_scope City
52
56
  field :name, type: 'keyword'
53
57
  field :rating, type: :integer
@@ -114,5 +118,10 @@ describe Chewy::Search do
114
118
  specify { expect(CountriesIndex.by_rating(3).by_name(5).map(&:class)).to eq([CountriesIndex]) }
115
119
  specify { expect(CountriesIndex.order(:name).by_rating(3).map(&:rating)).to eq([3]) }
116
120
  specify { expect(CountriesIndex.order(:name).by_rating(3).map(&:class)).to eq([CountriesIndex]) }
121
+
122
+ specify 'supports keyword arguments' do
123
+ expect(CitiesIndex.by_rating_with_kwargs(3, options: 'blah blah blah').map(&:rating)).to eq([3])
124
+ expect(CitiesIndex.order(:name).by_rating_with_kwargs(3, options: 'blah blah blah').map(&:rating)).to eq([3])
125
+ end
117
126
  end
118
127
  end