chewy 0.7.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
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