chewy 0.7.0 → 0.8.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 (54) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/.rspec +0 -1
  4. data/.travis.yml +2 -2
  5. data/Appraisals +6 -2
  6. data/CHANGELOG.md +29 -1
  7. data/Gemfile +4 -0
  8. data/README.md +137 -19
  9. data/chewy.gemspec +1 -0
  10. data/gemfiles/rails.3.2.activerecord.gemfile +2 -0
  11. data/gemfiles/rails.3.2.activerecord.kaminari.gemfile +1 -1
  12. data/gemfiles/rails.4.0.activerecord.gemfile +2 -0
  13. data/gemfiles/rails.4.0.activerecord.kaminari.gemfile +1 -1
  14. data/gemfiles/rails.4.0.mongoid.gemfile +2 -0
  15. data/gemfiles/rails.4.0.mongoid.kaminari.gemfile +1 -1
  16. data/gemfiles/rails.4.1.activerecord.gemfile +2 -0
  17. data/gemfiles/rails.4.1.activerecord.kaminari.gemfile +1 -1
  18. data/gemfiles/rails.4.1.mongoid.gemfile +2 -0
  19. data/gemfiles/rails.4.1.mongoid.kaminari.gemfile +1 -1
  20. data/gemfiles/rails.4.2.activerecord.gemfile +2 -0
  21. data/gemfiles/rails.4.2.activerecord.kaminari.gemfile +1 -1
  22. data/gemfiles/rails.4.2.mongoid.gemfile +2 -0
  23. data/gemfiles/rails.4.2.mongoid.kaminari.gemfile +1 -1
  24. data/lib/chewy.rb +1 -2
  25. data/lib/chewy/config.rb +3 -3
  26. data/lib/chewy/fields/base.rb +27 -30
  27. data/lib/chewy/fields/root.rb +9 -19
  28. data/lib/chewy/query.rb +34 -1
  29. data/lib/chewy/railtie.rb +1 -0
  30. data/lib/chewy/rspec/update_index.rb +16 -6
  31. data/lib/chewy/strategy.rb +12 -0
  32. data/lib/chewy/strategy/atomic.rb +1 -1
  33. data/lib/chewy/strategy/resque.rb +26 -0
  34. data/lib/chewy/strategy/sidekiq.rb +26 -0
  35. data/lib/chewy/strategy/urgent.rb +1 -1
  36. data/lib/chewy/type.rb +2 -0
  37. data/lib/chewy/type/adapter/active_record.rb +7 -3
  38. data/lib/chewy/type/adapter/mongoid.rb +5 -0
  39. data/lib/chewy/type/adapter/orm.rb +1 -1
  40. data/lib/chewy/type/crutch.rb +31 -0
  41. data/lib/chewy/type/import.rb +7 -6
  42. data/lib/chewy/type/mapping.rb +7 -3
  43. data/lib/chewy/type/observe.rb +24 -35
  44. data/lib/chewy/version.rb +1 -1
  45. data/spec/chewy/fields/base_spec.rb +26 -19
  46. data/spec/chewy/query_spec.rb +13 -0
  47. data/spec/chewy/runtime_spec.rb +1 -1
  48. data/spec/chewy/strategy/resque_spec.rb +35 -0
  49. data/spec/chewy/strategy/sidekiq_spec.rb +35 -0
  50. data/spec/chewy/type/adapter/mongoid_spec.rb +18 -9
  51. data/spec/chewy/type/mapping_spec.rb +14 -9
  52. data/spec/chewy/type/observe_spec.rb +22 -7
  53. data/spec/spec_helper.rb +1 -0
  54. metadata +23 -2
@@ -24,7 +24,7 @@ module Chewy
24
24
  end
25
25
 
26
26
  def identify collection
27
- ids = if collection.is_a?(relation_class)
27
+ if collection.is_a?(relation_class)
28
28
  pluck_ids(collection)
29
29
  else
30
30
  Array.wrap(collection).map do |entity|
@@ -0,0 +1,31 @@
1
+ module Chewy
2
+ class Type
3
+ module Crutch
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ class_attribute :_crutches
8
+ self._crutches = {}
9
+ end
10
+
11
+ class Crutches
12
+ def initialize type, collection
13
+ @type, @collection = type, collection
14
+ @type._crutches.keys.each do |name|
15
+ singleton_class.class_eval <<-METHOD, __FILE__, __LINE__ + 1
16
+ def #{name}
17
+ @#{name} ||= @type._crutches[:#{name}].call @collection
18
+ end
19
+ METHOD
20
+ end
21
+ end
22
+ end
23
+
24
+ module ClassMethods
25
+ def crutch name, &block
26
+ self._crutches = _crutches.merge(name.to_sym => block)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -78,11 +78,12 @@ module Chewy
78
78
  def bulk_body(action_objects, indexed_objects = nil)
79
79
  action_objects.inject([]) do |result, (action, objects)|
80
80
  method = "#{action}_bulk_entry"
81
- result.concat(objects.map { |object| send(method, object, indexed_objects) }.flatten)
81
+ crutches = Chewy::Type::Crutch::Crutches.new self, objects
82
+ result.concat(objects.map { |object| send(method, object, indexed_objects, crutches) }.flatten)
82
83
  end
83
84
  end
84
85
 
85
- def delete_bulk_entry(object, indexed_objects = nil)
86
+ def delete_bulk_entry(object, indexed_objects = nil, crutches = nil)
86
87
  entry = {}
87
88
 
88
89
  if self.root_object.id
@@ -102,7 +103,7 @@ module Chewy
102
103
  [{ delete: entry }]
103
104
  end
104
105
 
105
- def index_bulk_entry(object, indexed_objects = nil)
106
+ def index_bulk_entry(object, indexed_objects = nil, crutches = nil)
106
107
  entry = {}
107
108
 
108
109
  if self.root_object.id
@@ -119,7 +120,7 @@ module Chewy
119
120
  existing_object = entry[:_id].present? && indexed_objects && indexed_objects[entry[:_id].to_s]
120
121
  end
121
122
 
122
- entry[:data] = object_data(object)
123
+ entry[:data] = object_data(object, crutches)
123
124
 
124
125
  if existing_object && entry[:parent].to_s != existing_object[:parent]
125
126
  [{ delete: entry.except(:data).merge(parent: existing_object[:parent]) }, { index: entry }]
@@ -148,8 +149,8 @@ module Chewy
148
149
  end
149
150
  end
150
151
 
151
- def object_data object
152
- (self.root_object ||= build_root).compose(object)[type_name.to_sym]
152
+ def object_data object, crutches = nil
153
+ (self.root_object ||= build_root).compose(object, crutches)[type_name.to_sym]
153
154
  end
154
155
 
155
156
  def extract_errors result
@@ -154,7 +154,11 @@ module Chewy
154
154
  private
155
155
 
156
156
  def expand_nested field, &block
157
- @_current_field.nested(field) if @_current_field
157
+ if @_current_field
158
+ field.parent = @_current_field
159
+ @_current_field.children.push(field)
160
+ end
161
+
158
162
  if block
159
163
  previous_field, @_current_field = @_current_field, field
160
164
  block.call
@@ -164,8 +168,8 @@ module Chewy
164
168
 
165
169
  def build_root options = {}, &block
166
170
  self.root_object = Chewy::Fields::Root.new(type_name, options)
167
- expand_nested(self.root_object, &block)
168
- @_current_field = self.root_object
171
+ expand_nested(root_object, &block)
172
+ @_current_field = root_object
169
173
  end
170
174
  end
171
175
  end
@@ -3,52 +3,41 @@ module Chewy
3
3
  module Observe
4
4
  extend ActiveSupport::Concern
5
5
 
6
- module MongoidMethods
7
- def update_index(type_name, *args, &block)
8
- options = args.extract_options!
9
- method = args.first
10
-
11
- update = Proc.new do
12
- backreference = if method && method.to_s == 'self'
13
- self
14
- elsif method
15
- send(method)
16
- else
17
- instance_eval(&block)
18
- end
19
-
20
- Chewy.derive_type(type_name).update_index(backreference, options)
6
+ def self.update_proc(type_name, *args, &block)
7
+ options = args.extract_options!
8
+ method = args.first
9
+
10
+ Proc.new do
11
+ backreference = if method && method.to_s == 'self'
12
+ self
13
+ elsif method
14
+ send(method)
15
+ else
16
+ instance_eval(&block)
21
17
  end
22
18
 
23
- after_save &update
24
- after_destroy &update
19
+ Chewy.derive_type(type_name).update_index(backreference, options)
25
20
  end
26
21
  end
27
22
 
28
- module ActiveRecordMethods
23
+ module MongoidMethods
29
24
  def update_index(type_name, *args, &block)
30
- options = args.extract_options!
31
- method = args.first
25
+ update_proc = Observe.update_proc(type_name, *args, &block)
32
26
 
33
- update = Proc.new do
34
- # clear_association_cache if Chewy.strategy.current.name == :urgent
35
-
36
- backreference = if method && method.to_s == 'self'
37
- self
38
- elsif method
39
- send(method)
40
- else
41
- instance_eval(&block)
42
- end
27
+ after_save &update_proc
28
+ after_destroy &update_proc
29
+ end
30
+ end
43
31
 
44
- Chewy.derive_type(type_name).update_index(backreference, options)
45
- end
32
+ module ActiveRecordMethods
33
+ def update_index(type_name, *args, &block)
34
+ update_proc = Observe.update_proc(type_name, *args, &block)
46
35
 
47
36
  if Chewy.use_after_commit_callbacks
48
- after_commit &update
37
+ after_commit &update_proc
49
38
  else
50
- after_save &update
51
- after_destroy &update
39
+ after_save &update_proc
40
+ after_destroy &update_proc
52
41
  end
53
42
  end
54
43
  end
@@ -1,3 +1,3 @@
1
1
  module Chewy
2
- VERSION = '0.7.0'
2
+ VERSION = '0.8.0'
3
3
  end
@@ -14,9 +14,9 @@ describe Chewy::Fields::Base do
14
14
 
15
15
  context 'nested fields' do
16
16
  before do
17
- field.nested(described_class.new(:subname1, value: ->(o){ o.subvalue1 }))
18
- field.nested(described_class.new(:subname2, value: ->{ subvalue2 }))
19
- field.nested(described_class.new(:subname3))
17
+ field.children.push(described_class.new(:subname1, value: ->(o){ o.subvalue1 }))
18
+ field.children.push(described_class.new(:subname2, value: ->{ subvalue2 }))
19
+ field.children.push(described_class.new(:subname3))
20
20
  end
21
21
 
22
22
  specify { expect(field.compose(double(value: double(subvalue1: 'hello', subvalue2: 'value', subname3: 'world'))))
@@ -32,8 +32,13 @@ describe Chewy::Fields::Base do
32
32
 
33
33
  context 'parent objects' do
34
34
  let!(:country) { described_class.new(:name, value: ->(country){ country.cities }) }
35
- let!(:city) { country.nested(described_class.new(:name, value: ->(city, country) { city.districts.map { |district| [district, country.name] } })) }
36
- let!(:district) { city.nested(described_class.new(:name, value: ->(district, city, country) { [district, city.name, country.name] })) }
35
+ let!(:city) { described_class.new(:name, value: ->(city, country) { city.districts.map { |district| [district, country.name] } }) }
36
+ let!(:district) { described_class.new(:name, value: ->(district, city, country) { [district, city.name, country.name] }) }
37
+
38
+ before do
39
+ country.children.push(city)
40
+ city.children.push(district)
41
+ end
37
42
 
38
43
  specify { expect(country.compose(double(name: 'Thailand', cities: [
39
44
  double(name: 'Bangkok', districts: ['First', 'Second'])
@@ -48,8 +53,8 @@ describe Chewy::Fields::Base do
48
53
  context 'implicit values' do
49
54
  let(:field) { described_class.new(:name, type: 'string') }
50
55
  before do
51
- field.nested(described_class.new(:name))
52
- field.nested(described_class.new(:untouched))
56
+ field.children.push(described_class.new(:name))
57
+ field.children.push(described_class.new(:untouched))
53
58
  end
54
59
 
55
60
  specify { expect(field.compose(double(name: 'Alex'))).to eq({name: 'Alex'}) }
@@ -60,28 +65,21 @@ describe Chewy::Fields::Base do
60
65
  let(:object) { double(name: { key1: 'value1', key2: 'value2' }) }
61
66
 
62
67
  before do
63
- field.nested(described_class.new(:key1, value: ->(h){ h[:key1] }))
64
- field.nested(described_class.new(:key2, value: ->(h){ h[:key2] }))
68
+ field.children.push(described_class.new(:key1, value: ->(h){ h[:key1] }))
69
+ field.children.push(described_class.new(:key2, value: ->(h){ h[:key2] }))
65
70
  end
66
71
 
67
72
  specify{ expect(field.compose(object)).to eq({ name: { 'key1' => 'value1', 'key2' => 'value2' } }) }
68
73
  end
69
74
  end
70
75
 
71
- describe '#nested' do
72
- let(:field) { described_class.new(:name) }
73
-
74
- specify { expect { field.nested(described_class.new(:name1)) }
75
- .to change { field.nested[:name1] }.from(nil).to(an_instance_of(described_class)) }
76
- end
77
-
78
76
  describe '#mappings_hash' do
79
77
  let(:field) { described_class.new(:name, type: :object) }
80
78
  let(:fields1) { 2.times.map { |i| described_class.new("name#{i+1}", type: "string#{i+1}") } }
81
79
  let(:fields2) { 2.times.map { |i| described_class.new("name#{i+3}", type: "string#{i+3}") } }
82
80
  before do
83
- fields1.each { |m| field.nested(m) }
84
- fields2.each { |m| fields1[0].nested(m) }
81
+ fields1.each { |m| field.children.push(m) }
82
+ fields2.each { |m| fields1[0].children.push(m) }
85
83
  end
86
84
 
87
85
  specify { expect(field.mappings_hash).to eq({name: {type: :object, properties: {
@@ -317,7 +315,16 @@ describe Chewy::Fields::Base do
317
315
  stub_model(:country)
318
316
 
319
317
  City.belongs_to :country
320
- Country.has_many :cities
318
+
319
+ if active_record?
320
+ if ActiveRecord::VERSION::MAJOR >= 4
321
+ Country.has_many :cities, -> { order :id }
322
+ else
323
+ Country.has_many :cities, order: :id
324
+ end
325
+ else # mongoid
326
+ Country.has_many :cities, order: :id.asc
327
+ end
321
328
 
322
329
  stub_index(:countries) do
323
330
  define_type Country do
@@ -39,6 +39,8 @@ describe Chewy::Query do
39
39
  specify { expect(subject.types(:product, :country).count).to eq(6) }
40
40
  specify { expect(subject.filter(term: {age: 10}).count).to eq(1) }
41
41
  specify { expect(subject.query(term: {age: 10}).count).to eq(1) }
42
+ specify { expect(subject.search_type(:count).count).to eq(0) }
43
+ specify { expect(subject.search_type(:count).total).to eq(9) }
42
44
  end
43
45
 
44
46
  describe '#==' do
@@ -101,6 +103,13 @@ describe Chewy::Query do
101
103
  specify { expect { subject.offset(10) }.not_to change { subject.criteria.request_options } }
102
104
  end
103
105
 
106
+ describe '#script_fields' do
107
+ specify { expect(subject.script_fields(distance: 'test()')).to be_a described_class }
108
+ specify { expect(subject.script_fields(distance: 'test()')).not_to eq(subject) }
109
+ specify { expect(subject.script_fields(distance: 'test()').criteria.script_fields).to include(distance: 'test()') }
110
+ specify { expect { subject.script_fields(distance: 'test()') }.not_to change { subject.criteria.script_fields } }
111
+ end
112
+
104
113
  describe '#script_score' do
105
114
  specify { expect(subject.script_score('23')).to be_a described_class }
106
115
  specify { expect(subject.script_score('23')).not_to eq(subject) }
@@ -422,6 +431,10 @@ describe Chewy::Query do
422
431
  specify { expect(subject.types([:product, :city]).types!(:country).criteria.types).to match_array(['country']) }
423
432
  end
424
433
 
434
+ describe '#search_type' do
435
+ specify { expect(subject.search_type(:count).options).to include(search_type: :count) }
436
+ end
437
+
425
438
  describe '#aggregations' do
426
439
  specify { expect(subject.aggregations(attribute: {terms: {field: 'attribute'}})).to be_a described_class }
427
440
  specify { expect(subject.aggregations(attribute: {terms: {field: 'attribute'}})).not_to eq(subject) }
@@ -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.5' }
7
+ specify { expect(described_class.version).to be < '1.6' }
8
8
  end
9
9
  end
@@ -0,0 +1,35 @@
1
+ require 'spec_helper'
2
+
3
+ if defined?(::Resque)
4
+ require 'resque_spec'
5
+
6
+ describe Chewy::Strategy::Resque do
7
+ around { |example| Chewy.strategy(:bypass) { example.run } }
8
+ before { ResqueSpec.reset! }
9
+ before do
10
+ stub_model(:city) do
11
+ update_index('cities#city') { self }
12
+ end
13
+
14
+ stub_index(:cities) do
15
+ define_type City
16
+ end
17
+ end
18
+
19
+ let(:city) { City.create!(name: 'hello') }
20
+ let(:other_city) { City.create!(name: 'world') }
21
+
22
+ specify do
23
+ expect { [city, other_city].map(&:save!) }
24
+ .not_to update_index(CitiesIndex::City, strategy: :resque)
25
+ end
26
+
27
+ specify do
28
+ with_resque do
29
+ expect { [city, other_city].map(&:save!) }
30
+ .to update_index(CitiesIndex::City, strategy: :resque)
31
+ .and_reindex(city, other_city)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,35 @@
1
+ require 'spec_helper'
2
+
3
+ if defined?(::Sidekiq)
4
+ require 'sidekiq/testing'
5
+
6
+ describe Chewy::Strategy::Sidekiq do
7
+ around { |example| Chewy.strategy(:bypass) { example.run } }
8
+ before { ::Sidekiq::Worker.clear_all }
9
+ before do
10
+ stub_model(:city) do
11
+ update_index('cities#city') { self }
12
+ end
13
+
14
+ stub_index(:cities) do
15
+ define_type City
16
+ end
17
+ end
18
+
19
+ let(:city) { City.create!(name: 'hello') }
20
+ let(:other_city) { City.create!(name: 'world') }
21
+
22
+ specify do
23
+ expect { [city, other_city].map(&:save!) }
24
+ .not_to update_index(CitiesIndex::City, strategy: :sidekiq)
25
+ end
26
+
27
+ specify do
28
+ ::Sidekiq::Testing.inline! do
29
+ expect { [city, other_city].map(&:save!) }
30
+ .to update_index(CitiesIndex::City, strategy: :sidekiq)
31
+ .and_reindex(city, other_city)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -41,10 +41,19 @@ describe Chewy::Type::Adapter::Mongoid, :mongoid do
41
41
  subject { described_class.new(City) }
42
42
  let!(:cities) { 3.times.map { City.create! } }
43
43
 
44
- specify { expect(subject.identify(City.all)).to match_array(cities.map(&:id)) }
45
- specify { expect(subject.identify(cities)).to eq(cities.map(&:id)) }
46
- specify { expect(subject.identify(cities.first)).to eq([cities.first.id]) }
47
- specify { expect(subject.identify(cities.first(2).map(&:id))).to eq(cities.first(2).map(&:id)) }
44
+ specify { expect(subject.identify(City.all)).to match_array(cities.map(&:id).map(&:to_s)) }
45
+ specify { expect(subject.identify(cities)).to eq(cities.map(&:id).map(&:to_s)) }
46
+ specify { expect(subject.identify(cities.first)).to eq([cities.first.id.to_s]) }
47
+ specify { expect(subject.identify(cities.first(2).map(&:id))).to eq(cities.first(2).map(&:id).map(&:to_s)) }
48
+
49
+ context 'non-bson ids' do
50
+ let!(:cities) { 3.times.map { |i| City.create! id: i+1 } }
51
+
52
+ specify { expect(subject.identify(City.all)).to match_array(cities.map(&:id)) }
53
+ specify { expect(subject.identify(cities)).to eq(cities.map(&:id)) }
54
+ specify { expect(subject.identify(cities.first)).to eq([cities.first.id]) }
55
+ specify { expect(subject.identify(cities.first(2).map(&:id))).to eq(cities.first(2).map(&:id)) }
56
+ end
48
57
  end
49
58
 
50
59
  describe '#import' do
@@ -59,7 +68,7 @@ describe Chewy::Type::Adapter::Mongoid, :mongoid do
59
68
  let!(:deleted) { 4.times.map { |i| City.create!.tap(&:destroy) }.sort_by(&:id) }
60
69
  subject { described_class.new(City) }
61
70
 
62
- specify { expect(import).to eq([{index: cities}]) }
71
+ specify { expect(import).to match([{index: match_array(cities)}]) }
63
72
  specify { expect(import nil).to eq([]) }
64
73
 
65
74
  specify { expect(import(City.order(:id.asc))).to eq([{index: cities}]) }
@@ -145,8 +154,8 @@ describe Chewy::Type::Adapter::Mongoid, :mongoid do
145
154
  end
146
155
 
147
156
  context 'default scope' do
148
- let!(:cities) { 4.times.map { |i| City.create!(rating: i/3) }.sort_by(&:id) }
149
- let!(:deleted) { 3.times.map { |i| City.create!.tap(&:destroy) }.sort_by(&:id) }
157
+ let!(:cities) { 4.times.map { |i| City.create!(id: i, rating: i/3) }.sort_by(&:id) }
158
+ let!(:deleted) { 3.times.map { |i| City.create!(id: 4 + i).tap(&:destroy) }.sort_by(&:id) }
150
159
  subject { described_class.new(City.where(rating: 0)) }
151
160
 
152
161
  specify { expect(import).to eq([{index: cities.first(3)}]) }
@@ -242,8 +251,8 @@ describe Chewy::Type::Adapter::Mongoid, :mongoid do
242
251
 
243
252
  describe '#load' do
244
253
  context do
245
- let!(:cities) { 3.times.map { |i| City.create!(rating: i/2) }.sort_by(&:id) }
246
- let!(:deleted) { 2.times.map { |i| City.create!.tap(&:destroy) }.sort_by(&:id) }
254
+ let!(:cities) { 3.times.map { |i| City.create!(id: i, rating: i/2) }.sort_by(&:id) }
255
+ let!(:deleted) { 2.times.map { |i| City.create!(id: 3 + i).tap(&:destroy) }.sort_by(&:id) }
247
256
 
248
257
  let(:type) { double(type_name: 'user') }
249
258