chewy 0.8.1 → 0.8.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +10 -8
  3. data/Appraisals +8 -0
  4. data/CHANGELOG.md +33 -6
  5. data/Gemfile +6 -4
  6. data/README.md +240 -111
  7. data/gemfiles/rails.4.2.activerecord.gemfile +1 -0
  8. data/gemfiles/rails.4.2.activerecord.kaminari.gemfile +1 -0
  9. data/gemfiles/rails.4.2.activerecord.will_paginate.gemfile +1 -0
  10. data/gemfiles/sequel.4.23.gemfile +13 -0
  11. data/lib/chewy.rb +24 -11
  12. data/lib/chewy/config.rb +4 -4
  13. data/lib/chewy/index.rb +1 -1
  14. data/lib/chewy/index/settings.rb +1 -1
  15. data/lib/chewy/query.rb +43 -4
  16. data/lib/chewy/railtie.rb +2 -2
  17. data/lib/chewy/rake_helper.rb +62 -0
  18. data/lib/chewy/rspec/update_index.rb +3 -3
  19. data/lib/chewy/strategy.rb +6 -0
  20. data/lib/chewy/strategy/active_job.rb +28 -0
  21. data/lib/chewy/strategy/atomic.rb +1 -1
  22. data/lib/chewy/strategy/sidekiq.rb +3 -1
  23. data/lib/chewy/type.rb +2 -1
  24. data/lib/chewy/type/adapter/active_record.rb +9 -2
  25. data/lib/chewy/type/adapter/base.rb +6 -0
  26. data/lib/chewy/type/adapter/mongoid.rb +7 -1
  27. data/lib/chewy/type/adapter/orm.rb +1 -1
  28. data/lib/chewy/type/adapter/sequel.rb +125 -0
  29. data/lib/chewy/type/mapping.rb +26 -1
  30. data/lib/chewy/type/observe.rb +40 -17
  31. data/lib/chewy/version.rb +1 -1
  32. data/lib/sequel/plugins/chewy_observe.rb +71 -0
  33. data/lib/tasks/chewy.rake +19 -61
  34. data/spec/chewy/config_spec.rb +9 -5
  35. data/spec/chewy/fields/base_spec.rb +21 -7
  36. data/spec/chewy/index/actions_spec.rb +5 -5
  37. data/spec/chewy/query_spec.rb +69 -0
  38. data/spec/chewy/runtime_spec.rb +1 -1
  39. data/spec/chewy/strategy/active_job_spec.rb +49 -0
  40. data/spec/chewy/strategy_spec.rb +2 -2
  41. data/spec/chewy/type/adapter/sequel_spec.rb +46 -0
  42. data/spec/chewy/type/import_spec.rb +4 -2
  43. data/spec/chewy/type/mapping_spec.rb +19 -0
  44. data/spec/chewy/type/observe_spec.rb +43 -14
  45. data/spec/chewy_spec.rb +2 -3
  46. data/spec/spec_helper.rb +6 -3
  47. data/spec/support/active_record.rb +5 -8
  48. data/spec/support/mongoid.rb +5 -8
  49. data/spec/support/sequel.rb +69 -0
  50. metadata +14 -3
@@ -21,15 +21,19 @@ describe Chewy::Config do
21
21
  .to change { Chewy.client.transport.logger }.to(logger) }
22
22
  specify { expect { subject.transport_logger = logger }
23
23
  .to change { subject.transport_logger }.to(logger) }
24
+ specify { expect { subject.transport_logger = logger }
25
+ .to change { subject.configuration[:logger] }.from(nil).to(logger) }
24
26
  end
25
27
 
26
28
  describe '#transport_tracer=' do
27
- let(:logger) { Logger.new('/dev/null') }
29
+ let(:tracer) { Logger.new('/dev/null') }
28
30
  after { subject.transport_tracer = nil }
29
31
 
30
- specify { expect { subject.transport_tracer = logger }
31
- .to change { Chewy.client.transport.tracer }.to(logger) }
32
- specify { expect { subject.transport_tracer = logger }
33
- .to change { subject.transport_tracer }.to(logger) }
32
+ specify { expect { subject.transport_tracer = tracer }
33
+ .to change { Chewy.client.transport.tracer }.to(tracer) }
34
+ specify { expect { subject.transport_tracer = tracer }
35
+ .to change { subject.transport_tracer }.to(tracer) }
36
+ specify { expect { subject.transport_tracer = tracer }
37
+ .to change { subject.configuration[:tracer] }.from(nil).to(tracer) }
34
38
  end
35
39
  end
@@ -314,16 +314,20 @@ describe Chewy::Fields::Base do
314
314
  stub_model(:city)
315
315
  stub_model(:country)
316
316
 
317
- City.belongs_to :country
318
-
319
- if active_record?
317
+ case adapter
318
+ when :active_record
319
+ City.belongs_to :country
320
320
  if ActiveRecord::VERSION::MAJOR >= 4
321
321
  Country.has_many :cities, -> { order :id }
322
322
  else
323
323
  Country.has_many :cities, order: :id
324
324
  end
325
- else # mongoid
325
+ when :mongoid
326
+ City.belongs_to :country
326
327
  Country.has_many :cities, order: :id.asc
328
+ when :sequel
329
+ City.many_to_one :country
330
+ Country.one_to_many :cities, order: :id
327
331
  end
328
332
 
329
333
  stub_index(:countries) do
@@ -337,10 +341,20 @@ describe Chewy::Fields::Base do
337
341
  end
338
342
  end
339
343
 
344
+ let(:country_with_cities) do
345
+ cities = [City.create!(id: 1, name: 'City1'), City.create!(id: 2, name: 'City2')]
346
+
347
+ if adapter == :sequel
348
+ Country.create(id: 1).tap do |country|
349
+ cities.each { |city| country.add_city(city) }
350
+ end
351
+ else
352
+ Country.create!(id: 1, cities: cities)
353
+ end
354
+ end
355
+
340
356
  specify do
341
- expect(CountriesIndex::Country.root_object.compose(
342
- Country.create!(id: 1, cities: [City.create!(id: 1, name: 'City1'), City.create!(id: 2, name: 'City2')])
343
- )).to eq({
357
+ expect(CountriesIndex::Country.root_object.compose(country_with_cities)).to eq({
344
358
  country: { 'id' => 1, 'cities' => [
345
359
  { 'id' => 1, 'name' => 'City1' }, { 'id' => 2, 'name' => 'City2' }
346
360
  ] }
@@ -54,8 +54,8 @@ describe Chewy::Index::Actions do
54
54
 
55
55
  context do
56
56
  before { DummiesIndex.create }
57
- specify { expect { DummiesIndex.create! }.to raise_error }
58
- specify { expect { DummiesIndex.create!('2013') }.to raise_error }
57
+ specify { expect { DummiesIndex.create! }.to raise_error(Elasticsearch::Transport::Transport::Errors::BadRequest).with_message(/\[\[dummies\] already exists\]/) }
58
+ specify { expect { DummiesIndex.create!('2013') }.to raise_error(Elasticsearch::Transport::Transport::Errors::BadRequest).with_message(/Invalid alias name \[dummies\]/) }
59
59
  end
60
60
 
61
61
  context do
@@ -64,7 +64,7 @@ describe Chewy::Index::Actions do
64
64
  specify { expect(Chewy.client.indices.exists(index: 'dummies_2013')).to eq(true) }
65
65
  specify { expect(DummiesIndex.aliases).to eq([]) }
66
66
  specify { expect(DummiesIndex.indexes).to eq(['dummies_2013']) }
67
- specify { expect { DummiesIndex.create!('2013') }.to raise_error }
67
+ specify { expect { DummiesIndex.create!('2013') }.to raise_error(Elasticsearch::Transport::Transport::Errors::BadRequest).with_message(/\[\[dummies_2013\] already exists\]/) }
68
68
  specify { expect(DummiesIndex.create!('2014')["acknowledged"]).to eq(true) }
69
69
 
70
70
  context do
@@ -128,8 +128,8 @@ describe Chewy::Index::Actions do
128
128
  end
129
129
 
130
130
  describe '.delete!' do
131
- specify { expect { DummiesIndex.delete! }.to raise_error }
132
- specify { expect { DummiesIndex.delete!('2013') }.to raise_error }
131
+ specify { expect { DummiesIndex.delete! }.to raise_error(Elasticsearch::Transport::Transport::Errors::NotFound) }
132
+ specify { expect { DummiesIndex.delete!('2013') }.to raise_error(Elasticsearch::Transport::Transport::Errors::NotFound) }
133
133
 
134
134
  context do
135
135
  before { DummiesIndex.create }
@@ -213,6 +213,75 @@ describe Chewy::Query do
213
213
  specify { expect(subject.aggregations(aggregation1: {field: 'hello'}).criteria.aggregations).to include(aggregation1: {field: 'hello'}) }
214
214
  specify { expect { subject.aggregations(aggregation1: {field: 'hello'}) }.not_to change { subject.criteria.aggregations } }
215
215
 
216
+ context 'when requesting a named aggregation' do
217
+
218
+ before do
219
+ stub_index(:products) do
220
+ define_type :product do
221
+ root do
222
+ field :name, 'surname'
223
+ field :title, type: 'string' do
224
+ field :subfield1
225
+ end
226
+ field 'price', type: 'float' do
227
+ field :subfield2
228
+ end
229
+ agg :uniquely_named_agg do
230
+ { min: { field: 'title.subfield1' } }
231
+ end
232
+ end
233
+ end
234
+ end
235
+ end
236
+ specify { expect(subject.aggregations(:uniquely_named_agg).criteria.aggregations).to include(uniquely_named_agg: { min: { field: 'title.subfield1' } }) }
237
+
238
+ context 'when more than one aggregation of the same name exists' do
239
+ before do
240
+ stub_index(:products) do
241
+ define_type :product do
242
+ root do
243
+ field :name, 'surname'
244
+ field :title, type: 'string' do
245
+ field :subfield1
246
+ end
247
+ field 'price', type: 'float' do
248
+ field :subfield2
249
+ end
250
+ agg :uniquely_named_agg do
251
+ { min: { field: 'title.subfield1' } }
252
+ end
253
+ agg :named_agg do
254
+ { avg: { field: 'title.subfield1' } }
255
+ end
256
+ end
257
+ end
258
+ define_type :review do
259
+ field :title, :body
260
+ field :comments do
261
+ field :message
262
+ field :rating, type: 'long'
263
+ end
264
+ agg :named_agg do
265
+ { avg: { field: 'comments.rating' } }
266
+ end
267
+ end
268
+ end
269
+ end
270
+
271
+ it "is the aggregation definition that was last defined" do
272
+ expect(subject.aggregations(:named_agg).criteria.aggregations).to include(named_agg: { avg: { field: 'comments.rating' } })
273
+ end
274
+
275
+ context "when the fully qualified aggregation name is provided" do
276
+ specify { expect(subject
277
+ .aggregations("products#product.named_agg")
278
+ .criteria
279
+ .aggregations)
280
+ .to include({ "products#product.named_agg" => { avg: { field: 'title.subfield1' } } }) }
281
+ end
282
+ end
283
+ end
284
+
216
285
  context 'results', :orm do
217
286
  before { stub_model(:city) }
218
287
  let(:cities) { 10.times.map { |i| City.create! id: i + 1, name: "name#{i}", rating: i % 3 } }
@@ -4,6 +4,6 @@ describe Chewy::Runtime do
4
4
  describe '.version' do
5
5
  specify { expect(described_class.version).to be_a(described_class::Version) }
6
6
  specify { expect(described_class.version).to be >= '1.0' }
7
- specify { expect(described_class.version).to be < '1.6' }
7
+ specify { expect(described_class.version).to be < '1.8' }
8
8
  end
9
9
  end
@@ -0,0 +1,49 @@
1
+ require 'spec_helper'
2
+
3
+ if defined?(::ActiveJob)
4
+ describe Chewy::Strategy::ActiveJob do
5
+ around { |example| Chewy.strategy(:bypass) { example.run } }
6
+ before(:all) do
7
+ ::ActiveJob::Base.logger = Chewy.logger
8
+ end
9
+ before do
10
+ ::ActiveJob::Base.queue_adapter = :test
11
+ ::ActiveJob::Base.queue_adapter.enqueued_jobs.clear
12
+ ::ActiveJob::Base.queue_adapter.performed_jobs.clear
13
+ end
14
+
15
+ before do
16
+ stub_model(:city) do
17
+ update_index('cities#city') { self }
18
+ end
19
+
20
+ stub_index(:cities) do
21
+ define_type City
22
+ end
23
+ end
24
+
25
+ let(:city) { City.create!(name: 'hello') }
26
+ let(:other_city) { City.create!(name: 'world') }
27
+
28
+ specify do
29
+ expect { [city, other_city].map(&:save!) }
30
+ .not_to update_index(CitiesIndex::City, strategy: :active_job)
31
+ end
32
+
33
+ specify do
34
+ Chewy.strategy(:active_job) do
35
+ [city, other_city].map(&:save!)
36
+ end
37
+ enqueued_job = ::ActiveJob::Base.queue_adapter.enqueued_jobs.first
38
+ expect(enqueued_job[:job]).to eq(Chewy::Strategy::ActiveJob::Worker)
39
+ expect(enqueued_job[:queue]).to eq('chewy')
40
+ end
41
+
42
+ specify do
43
+ ::ActiveJob::Base.queue_adapter = :inline
44
+ expect { [city, other_city].map(&:save!) }
45
+ .to update_index(CitiesIndex::City, strategy: :active_job)
46
+ .and_reindex(city, other_city)
47
+ end
48
+ end
49
+ end
@@ -14,7 +14,7 @@ describe Chewy::Strategy do
14
14
  end
15
15
 
16
16
  describe '#push' do
17
- specify { expect { strategy.push(:unexistant) }.to raise_error }
17
+ specify { expect { strategy.push(:unexistant) }.to raise_error(NameError).with_message(/uninitialized constant.*Unexistant/) }
18
18
 
19
19
  specify do
20
20
  expect { strategy.push(:atomic) }
@@ -24,7 +24,7 @@ describe Chewy::Strategy do
24
24
  end
25
25
 
26
26
  describe '#pop' do
27
- specify { expect { strategy.pop }.to raise_error }
27
+ specify { expect { strategy.pop }.to raise_error(RuntimeError).with_message(/Can't pop root strategy/) }
28
28
 
29
29
  specify do
30
30
  strategy.push(:urgent)
@@ -0,0 +1,46 @@
1
+ require 'spec_helper'
2
+
3
+ describe Chewy::Type::Adapter::Sequel, :sequel do
4
+
5
+ let(:adapter) { described_class }
6
+
7
+ before do
8
+ stub_model(:city)
9
+ stub_model(:country)
10
+ end
11
+
12
+ describe '#name' do
13
+
14
+ it { expect( adapter.new(City).name ).to eq 'City' }
15
+ it { expect( adapter.new(City.order(:id)).name ).to eq 'City' }
16
+ it { expect( adapter.new(City, name: 'town').name ).to eq 'Town' }
17
+
18
+ context do
19
+ before { stub_model('namespace/city') }
20
+
21
+ it { expect( adapter.new(Namespace::City).name ).to eq 'City' }
22
+ it { expect( adapter.new(Namespace::City.order(:id)).name ).to eq 'City' }
23
+ end
24
+ end
25
+
26
+ describe '#default_dataset' do
27
+
28
+ it { expect( adapter.new(City).default_dataset.sql ).to eql City.where(nil).sql }
29
+ it { expect( adapter.new(City.order(:id)).default_dataset.sql ).to eql City.where(nil).sql }
30
+ it { expect( adapter.new(City.limit(10)).default_dataset.sql ).to eql City.where(nil).sql }
31
+ it { expect( adapter.new(City.offset(10)).default_dataset.sql ).to eql City.where(nil).sql }
32
+ it { expect( adapter.new(City.where(rating: 10)).default_dataset.sql ).to eql City.where(rating: 10).sql }
33
+ end
34
+
35
+ describe '#identify' do
36
+ context do
37
+ subject(:s) { adapter.new(City) }
38
+ let!(:cities) { 3.times.map { City.new.save } }
39
+
40
+ it { expect( s.identify(City.where(nil)) ).to match_array cities.map(&:id) }
41
+ it { expect( s.identify(cities) ).to eq cities.map(&:id) }
42
+ it { expect( s.identify(cities.first) ).to eq([cities.first.id]) }
43
+ it { expect( s.identify(cities.first(2).map(&:pk)) ).to eq cities.first(2).map(&:id) }
44
+ end
45
+ end
46
+ end
@@ -164,7 +164,8 @@ describe Chewy::Type::Import do
164
164
  type: CitiesIndex::City,
165
165
  errors: {
166
166
  index: {
167
- 'MapperParsingException[object mapping for [city] tried to parse field [name] as object, but got EOF, has a concrete value been provided to it?]' => ['1', '2', '3']
167
+ "WriteFailureException; nested: MapperParsingException[object mapping for [city] tried to parse field [name] as object, but got EOF, has a concrete value been provided to it?]; " => ["1"],
168
+ "MapperParsingException[object mapping for [city] tried to parse field [name] as object, but got EOF, has a concrete value been provided to it?]" => ["2", "3"]
168
169
  }
169
170
  },
170
171
  import: {index: 3}
@@ -209,7 +210,8 @@ describe Chewy::Type::Import do
209
210
 
210
211
  before do
211
212
  stub_model(:country)
212
- stub_model(:city) { belongs_to :country }
213
+ stub_model(:city)
214
+ adapter == :sequel ? City.many_to_one(:country) : City.belongs_to(:country)
213
215
  end
214
216
 
215
217
  before do
@@ -2,6 +2,7 @@ require 'spec_helper'
2
2
 
3
3
  describe Chewy::Type::Mapping do
4
4
  let(:product) { ProductsIndex::Product }
5
+ let(:review) { ProductsIndex::Review }
5
6
 
6
7
  before do
7
8
  stub_index(:products) do
@@ -14,11 +15,29 @@ describe Chewy::Type::Mapping do
14
15
  field 'price', type: 'float' do
15
16
  field :subfield2
16
17
  end
18
+ agg :named_agg do
19
+ { avg: { field: 'title.subfield1' } }
20
+ end
21
+ end
22
+ end
23
+ define_type :review do
24
+ field :title, :body
25
+ field :comments do
26
+ field :message
27
+ field :rating, type: 'long'
28
+ end
29
+ agg :named_agg do
30
+ { avg: { field: 'comments.rating' } }
17
31
  end
18
32
  end
19
33
  end
20
34
  end
21
35
 
36
+ describe '.agg' do
37
+ specify { expect(product._agg_defs[:named_agg].call).to eq({ avg: { field: 'title.subfield1' } }) }
38
+ specify { expect(review._agg_defs[:named_agg].call).to eq({ avg: { field: 'comments.rating' } }) }
39
+ end
40
+
22
41
  describe '.field' do
23
42
  specify { expect(product.root_object.children.map(&:name)).to eq([:name, :surname, :title, :price]) }
24
43
  specify { expect(product.root_object.children.map(&:parent)).to eq([product.root_object] * 4) }
@@ -10,30 +10,43 @@ describe Chewy::Type::Import do
10
10
 
11
11
  let(:backreferenced) { 3.times.map { |i| double(id: i) } }
12
12
 
13
- specify { expect { DummiesIndex.dummy.update_index(backreferenced) }
13
+ specify { expect { DummiesIndex::Dummy.update_index(backreferenced) }
14
14
  .to raise_error Chewy::UndefinedUpdateStrategy }
15
- specify { expect { DummiesIndex.dummy.update_index([]) }
15
+ specify { expect { DummiesIndex::Dummy.update_index([]) }
16
16
  .not_to update_index('dummies#dummy') }
17
- specify { expect { DummiesIndex.dummy.update_index(nil) }
17
+ specify { expect { DummiesIndex::Dummy.update_index(nil) }
18
18
  .not_to update_index('dummies#dummy') }
19
19
  end
20
20
 
21
21
  context 'integration', :orm do
22
+ let(:update_condition) { true }
23
+
22
24
  before do
23
- stub_model(:city) do
24
- update_index('cities#city') { self }
25
- update_index 'countries#country' do
26
- changes['country_id'] || previous_changes['country_id'] || country
25
+ city_countries_update_proc = if adapter == :sequel
26
+ ->(*) { previous_changes.try(:[], :country_id) || country }
27
+ else
28
+ ->(*) { changes['country_id'] || previous_changes['country_id'] || country }
27
29
  end
30
+
31
+ stub_model(:city) do
32
+ update_index(->(city) { "cities##{city.class.name.underscore}" }) { self }
33
+ update_index 'countries#country', &city_countries_update_proc
28
34
  end
29
35
 
30
36
  stub_model(:country) do
31
- update_index('cities#city') { cities }
32
- update_index 'countries#country', :self
37
+ update_index('cities#city', if: -> { update_condition }) { cities }
38
+ update_index(->{ "countries##{self.class.name.underscore}" }, :self)
39
+ attr_accessor :update_condition
33
40
  end
34
41
 
35
- City.belongs_to :country
36
- Country.has_many :cities
42
+ if adapter == :sequel
43
+ City.many_to_one :country
44
+ Country.one_to_many :cities
45
+ City.plugin :dirty
46
+ else
47
+ City.belongs_to :country
48
+ Country.has_many :cities
49
+ end
37
50
 
38
51
  stub_index(:cities) do
39
52
  define_type City
@@ -45,8 +58,8 @@ describe Chewy::Type::Import do
45
58
  end
46
59
 
47
60
  context do
48
- let!(:country1) { Chewy.strategy(:atomic) { Country.create!(id: 1) } }
49
- let!(:country2) { Chewy.strategy(:atomic) { Country.create!(id: 2) } }
61
+ let!(:country1) { Chewy.strategy(:atomic) { Country.create!(id: 1, update_condition: update_condition) } }
62
+ let!(:country2) { Chewy.strategy(:atomic) { Country.create!(id: 2, update_condition: update_condition) } }
50
63
  let!(:city) { Chewy.strategy(:atomic) { City.create!(id: 1, country: country1) } }
51
64
 
52
65
  specify { expect { city.save! }.to update_index('cities#city').and_reindex(city) }
@@ -60,10 +73,26 @@ describe Chewy::Type::Import do
60
73
  end
61
74
 
62
75
  context do
63
- let!(:country) { Chewy.strategy(:atomic) { Country.create!(id: 1, cities: 2.times.map { |i| City.create!(id: i) }) } }
76
+ let!(:country) do
77
+ Chewy.strategy(:atomic) do
78
+ cities = 2.times.map { |i| City.create!(id: i) }
79
+ if adapter == :sequel
80
+ Country.create(id: 1, update_condition: update_condition).tap do |country|
81
+ cities.each { |city| country.add_city(city) }
82
+ end
83
+ else
84
+ Country.create!(id: 1, cities: cities, update_condition: update_condition)
85
+ end
86
+ end
87
+ end
64
88
 
65
89
  specify { expect { country.save! }.to update_index('cities#city').and_reindex(country.cities) }
66
90
  specify { expect { country.save! }.to update_index('countries#country').and_reindex(country) }
91
+
92
+ context 'conditional update' do
93
+ let(:update_condition) { false }
94
+ specify { expect { country.save! }.not_to update_index('cities#city') }
95
+ end
67
96
  end
68
97
  end
69
98