chewy 0.6.2 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (101) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -1
  3. data/.travis.yml +35 -29
  4. data/Appraisals +37 -0
  5. data/CHANGELOG.md +115 -4
  6. data/Gemfile +2 -3
  7. data/README.md +135 -40
  8. data/chewy.gemspec +4 -3
  9. data/gemfiles/rails.3.2.activerecord.gemfile +13 -0
  10. data/gemfiles/rails.3.2.activerecord.kaminari.gemfile +14 -0
  11. data/gemfiles/rails.3.2.activerecord.will_paginate.gemfile +14 -0
  12. data/gemfiles/rails.4.0.activerecord.gemfile +13 -0
  13. data/gemfiles/rails.4.0.activerecord.kaminari.gemfile +14 -0
  14. data/gemfiles/rails.4.0.activerecord.will_paginate.gemfile +14 -0
  15. data/gemfiles/rails.4.0.mongoid.gemfile +13 -0
  16. data/gemfiles/rails.4.0.mongoid.kaminari.gemfile +14 -0
  17. data/gemfiles/rails.4.0.mongoid.will_paginate.gemfile +14 -0
  18. data/gemfiles/rails.4.1.activerecord.gemfile +13 -0
  19. data/gemfiles/rails.4.1.activerecord.kaminari.gemfile +14 -0
  20. data/gemfiles/rails.4.1.activerecord.will_paginate.gemfile +14 -0
  21. data/gemfiles/rails.4.1.mongoid.gemfile +13 -0
  22. data/gemfiles/rails.4.1.mongoid.kaminari.gemfile +14 -0
  23. data/gemfiles/rails.4.1.mongoid.will_paginate.gemfile +14 -0
  24. data/gemfiles/rails.4.2.activerecord.gemfile +13 -0
  25. data/gemfiles/rails.4.2.activerecord.kaminari.gemfile +14 -0
  26. data/gemfiles/rails.4.2.activerecord.will_paginate.gemfile +14 -0
  27. data/gemfiles/rails.4.2.mongoid.gemfile +13 -0
  28. data/gemfiles/rails.4.2.mongoid.kaminari.gemfile +14 -0
  29. data/gemfiles/rails.4.2.mongoid.will_paginate.gemfile +14 -0
  30. data/lib/chewy.rb +65 -0
  31. data/lib/chewy/config.rb +44 -93
  32. data/lib/chewy/errors.rb +14 -5
  33. data/lib/chewy/fields/base.rb +8 -7
  34. data/lib/chewy/fields/root.rb +2 -2
  35. data/lib/chewy/index.rb +7 -9
  36. data/lib/chewy/log_subscriber.rb +34 -0
  37. data/lib/chewy/query.rb +41 -27
  38. data/lib/chewy/query/criteria.rb +28 -23
  39. data/lib/chewy/query/scoping.rb +20 -0
  40. data/lib/chewy/railtie.rb +51 -13
  41. data/lib/chewy/repository.rb +61 -0
  42. data/lib/chewy/rspec/update_index.rb +3 -6
  43. data/lib/chewy/search.rb +28 -7
  44. data/lib/chewy/strategy.rb +60 -0
  45. data/lib/chewy/strategy/atomic.rb +31 -0
  46. data/lib/chewy/strategy/base.rb +27 -0
  47. data/lib/chewy/strategy/bypass.rb +15 -0
  48. data/lib/chewy/strategy/urgent.rb +17 -0
  49. data/lib/chewy/type.rb +19 -5
  50. data/lib/chewy/type/adapter/active_record.rb +28 -117
  51. data/lib/chewy/type/adapter/base.rb +35 -0
  52. data/lib/chewy/type/adapter/mongoid.rb +23 -123
  53. data/lib/chewy/type/adapter/object.rb +41 -19
  54. data/lib/chewy/type/adapter/orm.rb +142 -0
  55. data/lib/chewy/type/import.rb +43 -16
  56. data/lib/chewy/type/observe.rb +8 -21
  57. data/lib/chewy/version.rb +1 -1
  58. data/lib/tasks/chewy.rake +8 -4
  59. data/spec/chewy/config_spec.rb +20 -97
  60. data/spec/chewy/fields/base_spec.rb +24 -11
  61. data/spec/chewy/fields/time_fields_spec.rb +27 -0
  62. data/spec/chewy/index/settings_spec.rb +2 -1
  63. data/spec/chewy/index_spec.rb +98 -79
  64. data/spec/chewy/query/criteria_spec.rb +14 -0
  65. data/spec/chewy/query_spec.rb +1 -1
  66. data/spec/chewy/repository_spec.rb +50 -0
  67. data/spec/chewy/search_spec.rb +100 -0
  68. data/spec/chewy/strategy_spec.rb +109 -0
  69. data/spec/chewy/type/adapter/active_record_spec.rb +110 -46
  70. data/spec/chewy/type/adapter/mongoid_spec.rb +123 -74
  71. data/spec/chewy/type/adapter/object_spec.rb +51 -34
  72. data/spec/chewy/type/import_spec.rb +21 -21
  73. data/spec/chewy/type/observe_spec.rb +26 -29
  74. data/spec/chewy/type_spec.rb +19 -0
  75. data/spec/chewy_spec.rb +19 -3
  76. data/spec/spec_helper.rb +1 -1
  77. data/spec/support/active_record.rb +2 -1
  78. data/spec/support/mongoid.rb +29 -38
  79. metadata +85 -55
  80. data/gemfiles/Gemfile.rails-3.2.active_record +0 -6
  81. data/gemfiles/Gemfile.rails-3.2.active_record.kaminari +0 -7
  82. data/gemfiles/Gemfile.rails-3.2.active_record.will_paginate +0 -7
  83. data/gemfiles/Gemfile.rails-4.0.active_record +0 -6
  84. data/gemfiles/Gemfile.rails-4.0.active_record.kaminari +0 -7
  85. data/gemfiles/Gemfile.rails-4.0.active_record.will_paginate +0 -7
  86. data/gemfiles/Gemfile.rails-4.0.mongoid +0 -6
  87. data/gemfiles/Gemfile.rails-4.0.mongoid.kaminari +0 -7
  88. data/gemfiles/Gemfile.rails-4.0.mongoid.will_paginate +0 -7
  89. data/gemfiles/Gemfile.rails-4.1.active_record +0 -6
  90. data/gemfiles/Gemfile.rails-4.1.active_record.kaminari +0 -7
  91. data/gemfiles/Gemfile.rails-4.1.active_record.will_paginate +0 -7
  92. data/gemfiles/Gemfile.rails-4.1.mongoid +0 -6
  93. data/gemfiles/Gemfile.rails-4.1.mongoid.kaminari +0 -7
  94. data/gemfiles/Gemfile.rails-4.1.mongoid.will_paginate +0 -7
  95. data/gemfiles/Gemfile.rails-4.2.active_record +0 -6
  96. data/gemfiles/Gemfile.rails-4.2.active_record.kaminari +0 -7
  97. data/gemfiles/Gemfile.rails-4.2.active_record.will_paginate +0 -7
  98. data/gemfiles/Gemfile.rails-4.2.mongoid +0 -6
  99. data/gemfiles/Gemfile.rails-4.2.mongoid.kaminari +0 -7
  100. data/gemfiles/Gemfile.rails-4.2.mongoid.will_paginate +0 -7
  101. data/spec/chewy/index/search_spec.rb +0 -46
@@ -16,6 +16,14 @@ describe Chewy::Type::Adapter::Mongoid, :mongoid do
16
16
  end
17
17
  end
18
18
 
19
+ describe '#default_scope' do
20
+ specify { expect(described_class.new(City).default_scope).to eq(City.all) }
21
+ specify { expect(described_class.new(City.order(:id.asc)).default_scope).to eq(City.all) }
22
+ specify { expect(described_class.new(City.limit(10)).default_scope).to eq(City.all) }
23
+ specify { expect(described_class.new(City.offset(10)).default_scope).to eq(City.all) }
24
+ specify { expect(described_class.new(City.where(rating: 10)).default_scope).to eq(City.where(rating: 10)) }
25
+ end
26
+
19
27
  describe '#type_name' do
20
28
  specify { expect(described_class.new(City).type_name).to eq('city') }
21
29
  specify { expect(described_class.new(City.order(:id.asc)).type_name).to eq('city') }
@@ -29,6 +37,16 @@ describe Chewy::Type::Adapter::Mongoid, :mongoid do
29
37
  end
30
38
  end
31
39
 
40
+ describe '#identify' do
41
+ subject { described_class.new(City) }
42
+ let!(:cities) { 3.times.map { City.create! } }
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)) }
48
+ end
49
+
32
50
  describe '#import' do
33
51
  def import(*args)
34
52
  result = []
@@ -38,10 +56,11 @@ describe Chewy::Type::Adapter::Mongoid, :mongoid do
38
56
 
39
57
  context do
40
58
  let!(:cities) { 3.times.map { |i| City.create! }.sort_by(&:id) }
41
- let!(:deleted) { 3.times.map { |i| City.create!.tap(&:destroy) }.sort_by(&:id) }
59
+ let!(:deleted) { 4.times.map { |i| City.create!.tap(&:destroy) }.sort_by(&:id) }
42
60
  subject { described_class.new(City) }
43
61
 
44
62
  specify { expect(import).to eq([{index: cities}]) }
63
+ specify { expect(import nil).to eq([]) }
45
64
 
46
65
  specify { expect(import(City.order(:id.asc))).to eq([{index: cities}]) }
47
66
  specify { expect(import(City.order(:id.asc), batch_size: 2))
@@ -50,11 +69,13 @@ describe Chewy::Type::Adapter::Mongoid, :mongoid do
50
69
  specify { expect(import(cities)).to eq([{index: cities}]) }
51
70
  specify { expect(import(cities, batch_size: 2))
52
71
  .to eq([{index: cities.first(2)}, {index: cities.last(1)}]) }
53
- specify { expect(import(cities, deleted)).to eq([{index: cities, delete: deleted}]) }
72
+ specify { expect(import(cities, deleted))
73
+ .to eq([{index: cities}, {delete: deleted}]) }
54
74
  specify { expect(import(cities, deleted, batch_size: 2)).to eq([
55
- {index: cities.first(2)},
56
- {index: cities.last(1), delete: deleted.first(1)},
57
- {delete: deleted.last(2)}]) }
75
+ {index: cities.first(2)},
76
+ {index: cities.last(1)},
77
+ {delete: deleted.first(2)},
78
+ {delete: deleted.last(2)}]) }
58
79
 
59
80
  specify { expect(import(cities.map(&:id))).to eq([{index: cities}]) }
60
81
  specify { expect(import(deleted.map(&:id))).to eq([{delete: deleted.map(&:id)}]) }
@@ -66,127 +87,155 @@ describe Chewy::Type::Adapter::Mongoid, :mongoid do
66
87
  {index: cities.first(2)},
67
88
  {index: cities.last(1)},
68
89
  {delete: deleted.first(2).map(&:id)},
69
- {delete: deleted.last(1).map(&:id)}]) }
90
+ {delete: deleted.last(2).map(&:id)}]) }
70
91
 
71
92
  specify { expect(import(cities.first, nil)).to eq([{index: [cities.first]}]) }
72
93
  specify { expect(import(cities.first.id, nil)).to eq([{index: [cities.first]}]) }
94
+ end
73
95
 
74
- context do
75
- before { deleted.map { |object| allow(object).to receive_messages(delete_from_index?: true, destroyed?: true) } }
76
- specify { expect(import(deleted)).to eq([{delete: deleted}]) }
77
- end
96
+ context 'additional delete conitions' do
97
+ let!(:cities) { 4.times.map { |i| City.create! rating: i } }
98
+ before { cities.last(2).map(&:destroy) }
99
+ subject { described_class.new(City) }
78
100
 
79
101
  context do
80
- before { deleted.map { |object| allow(object).to receive_messages(delete_from_index?: true, destroyed?: false) } }
81
- specify { expect(import(deleted)).to eq([{delete: deleted}]) }
82
- end
102
+ before do
103
+ City.class_eval do
104
+ def delete_from_index?
105
+ rating.in?([1, 3])
106
+ end
107
+ end
108
+ end
83
109
 
84
- context do
85
- before { deleted.map { |object| allow(object).to receive_messages(delete_from_index?: false, destroyed?: true) } }
86
- specify { expect(import(deleted)).to eq([{delete: deleted}]) }
110
+ specify { expect(import(City.all)).to eq([
111
+ { index: [cities[0]], delete: [cities[1]] }
112
+ ]) }
113
+ specify { expect(import(cities)).to eq([
114
+ { index: [cities[0]], delete: [cities[1]] },
115
+ { delete: cities.last(2) }
116
+ ]) }
117
+ specify { expect(import(cities.map(&:id))).to eq([
118
+ { index: [cities[0]], delete: [cities[1]] },
119
+ { delete: cities.last(2).map(&:id) }
120
+ ]) }
87
121
  end
88
122
 
89
123
  context do
90
- before { deleted.map { |object| allow(object).to receive_messages(delete_from_index?: false, destroyed?: false) } }
91
- specify { expect(import(deleted)).to eq([{index: deleted}]) }
92
- end
93
- end
94
-
95
- describe '#delete_from_index?' do
96
- before do
97
- stub_model(:city) do
98
- def delete_from_index?
99
- rating == 42
124
+ before do
125
+ City.class_eval do
126
+ def delete_already?
127
+ rating.in?([1, 3])
128
+ end
100
129
  end
101
130
  end
131
+ subject { described_class.new(City, delete_if: ->{ delete_already? }) }
132
+
133
+ specify { expect(import(City.all)).to eq([
134
+ { index: [cities[0]], delete: [cities[1]] }
135
+ ]) }
136
+ specify { expect(import(cities)).to eq([
137
+ { index: [cities[0]], delete: [cities[1]] },
138
+ { delete: cities.last(2) }
139
+ ]) }
140
+ specify { expect(import(cities.map(&:id))).to eq([
141
+ { index: [cities[0]], delete: [cities[1]] },
142
+ { delete: cities.last(2).map(&:id) }
143
+ ]) }
102
144
  end
103
- let!(:cities) { 3.times.map { |i| City.create! }.sort_by(&:id) }
104
- let!(:deleted) { 3.times.map { |i| City.create!(rating: 42) }.sort_by(&:id) }
105
- subject { described_class.new(City) }
106
-
107
- specify { expect(import(cities, deleted)).to eq([{index: cities, delete: deleted}]) }
108
- specify { expect(import(cities.map(&:id), deleted.map(&:id)))
109
- .to eq([{index: cities, delete: deleted}]) }
110
- specify { expect(import(City.order(:id.asc))).to eq([{index: cities, delete: deleted}]) }
111
145
  end
112
146
 
113
147
  context 'default scope' do
114
- let!(:cities) { 3.times.map { |i| City.create!(rating: i/2) }.sort_by(&:id) }
115
- let!(:deleted) { 2.times.map { |i| City.create!.tap(&:destroy) }.sort_by(&:id) }
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) }
116
150
  subject { described_class.new(City.where(rating: 0)) }
117
151
 
118
- specify { expect(import).to eq([{index: cities.first(2)}]) }
152
+ specify { expect(import).to eq([{index: cities.first(3)}]) }
119
153
 
120
- specify { expect(import(City.order(:id.asc))).to eq([{index: cities.first(2)}]) }
121
- xspecify { expect(import(City.order(:id.asc), batch_size: 1))
122
- .to eq([{index: [cities.first]}, {index: [cities.second]}]) }
154
+ specify { expect(import(City.where(:rating.lt => 2).order(:id.asc)))
155
+ .to eq([{index: cities.first(3)}]) }
156
+ specify { expect(import(City.where(:rating.lt => 2).order(:id.asc), batch_size: 2))
157
+ .to eq([{index: cities.first(2)}, {index: [cities[2]]}]) }
158
+ specify { expect(import(City.where(:rating.lt => 1).order(:id.asc)))
159
+ .to eq([{index: cities.first(3)}]) }
160
+ specify { expect(import(City.where(:rating.gt => 1).order(:id.asc))).to eq([]) }
123
161
 
124
- specify { expect(import(cities)).to eq([{index: cities}]) }
162
+ specify { expect(import(cities.first(2)))
163
+ .to eq([{index: cities.first(2)}]) }
164
+ specify { expect(import(cities))
165
+ .to eq([{index: cities.first(3)}, {delete: cities.last(1)}]) }
125
166
  specify { expect(import(cities, batch_size: 2))
126
- .to eq([{index: cities.first(2)}, {index: cities.last(1)}]) }
127
-
167
+ .to eq([{index: cities.first(2)}, {index: [cities[2]]}, {delete: cities.last(1)}]) }
168
+ specify { expect(import(cities, deleted))
169
+ .to eq([{index: cities.first(3)}, {delete: cities.last(1) + deleted}]) }
170
+ specify { expect(import(cities, deleted, batch_size: 3)).to eq([
171
+ {index: cities.first(3)},
172
+ {delete: cities.last(1) + deleted.first(2)},
173
+ {delete: deleted.last(1)}]) }
174
+
175
+ specify { expect(import(cities.first(2).map(&:id)))
176
+ .to eq([{index: cities.first(2)}]) }
128
177
  specify { expect(import(cities.map(&:id)))
129
- .to eq([{index: cities.first(2)}, {delete: [cities.last.id]}]) }
130
- xspecify { expect(import(cities.map(&:id), batch_size: 1))
131
- .to eq([{index: [cities.first]}, {index: [cities.second]}, {delete: [cities.last.id]}]) }
178
+ .to eq([{index: cities.first(3)}, {delete: [cities.last.id]}]) }
179
+ specify { expect(import(cities.map(&:id), batch_size: 2))
180
+ .to eq([{index: cities.first(2)}, {index: [cities[2]]}, {delete: [cities.last.id]}]) }
132
181
  specify { expect(import(cities.map(&:id), deleted.map(&:id)))
133
- .to eq([{index: cities.first(2)}, {delete: [cities.last.id] + deleted.map(&:id)}]) }
134
- specify { expect(import(cities.map(&:id), deleted.map(&:id), batch_size: 2)).to eq([
135
- {index: cities.first(2)},
136
- {delete: [cities.last.id] + deleted.first(1).map(&:id)},
182
+ .to eq([{index: cities.first(3)}, {delete: [cities.last.id] + deleted.map(&:id)}]) }
183
+ specify { expect(import(cities.map(&:id), deleted.map(&:id), batch_size: 3)).to eq([
184
+ {index: cities.first(3)},
185
+ {delete: [cities.last.id] + deleted.first(2).map(&:id)},
137
186
  {delete: deleted.last(1).map(&:id)}]) }
138
187
  end
139
188
 
140
189
  context 'error handling' do
141
- let!(:cities) { 6.times.map { |i| City.create! }.sort_by(&:id) }
142
- let!(:deleted) { 4.times.map { |i| City.create!.tap(&:destroy) }.sort_by(&:id) }
190
+ let!(:cities) { 6.times.map { |i| City.create! } }
191
+ let!(:deleted) { 4.times.map { |i| City.create!.tap(&:destroy) } }
143
192
  let(:ids) { (cities + deleted).map(&:id) }
144
193
  subject { described_class.new(City) }
145
194
 
146
195
  let(:data_comparer) do
147
- ->(ids, data) { objects = (data[:index] || data[:delete]).first(2); objects.map { |o| o.respond_to?(:id) ? o.id : o }.sort != ids.map(&:id).sort }
196
+ ->(id, data) { objects = data[:index] || data[:delete]; !objects.map { |o| o.respond_to?(:id) ? o.id : o }.include?(id) }
148
197
  end
149
198
 
150
199
  context 'implicit scope' do
151
200
  specify { expect(subject.import { |data| true }).to eq(true) }
152
201
  specify { expect(subject.import { |data| false }).to eq(false) }
153
- specify { expect(subject.import(batch_size: 2, &data_comparer.curry[cities[0..1]])).to eq(false) }
154
- specify { expect(subject.import(batch_size: 2, &data_comparer.curry[cities[2..3]])).to eq(false) }
155
- specify { expect(subject.import(batch_size: 2, &data_comparer.curry[cities[4..5]])).to eq(false) }
156
- specify { expect(subject.import(batch_size: 2, &data_comparer.curry[deleted[0..1]])).to eq(true) }
157
- specify { expect(subject.import(batch_size: 2, &data_comparer.curry[deleted[2..3]])).to eq(true) }
202
+ specify { expect(subject.import(batch_size: 2, &data_comparer.curry[cities[0].id])).to eq(false) }
203
+ specify { expect(subject.import(batch_size: 2, &data_comparer.curry[cities[2].id])).to eq(false) }
204
+ specify { expect(subject.import(batch_size: 2, &data_comparer.curry[cities[4].id])).to eq(false) }
205
+ specify { expect(subject.import(batch_size: 2, &data_comparer.curry[deleted[0].id])).to eq(true) }
206
+ specify { expect(subject.import(batch_size: 2, &data_comparer.curry[deleted[2].id])).to eq(true) }
158
207
  end
159
208
 
160
209
  context 'explicit scope' do
161
- let(:scope) { mongoid? ? City.where(:id.in => ids) : City.where(id: ids) }
210
+ let(:scope) { City.where(:id.in => ids) }
162
211
 
163
212
  specify { expect(subject.import(scope) { |data| true }).to eq(true) }
164
213
  specify { expect(subject.import(scope) { |data| false }).to eq(false) }
165
- specify { expect(subject.import(scope, batch_size: 2, &data_comparer.curry[cities[0..1]])).to eq(false) }
166
- specify { expect(subject.import(scope, batch_size: 2, &data_comparer.curry[cities[2..3]])).to eq(false) }
167
- specify { expect(subject.import(scope, batch_size: 2, &data_comparer.curry[cities[4..5]])).to eq(false) }
168
- specify { expect(subject.import(scope, batch_size: 2, &data_comparer.curry[deleted[0..1]])).to eq(true) }
169
- specify { expect(subject.import(scope, batch_size: 2, &data_comparer.curry[deleted[2..3]])).to eq(true) }
214
+ specify { expect(subject.import(scope, batch_size: 2, &data_comparer.curry[cities[0].id])).to eq(false) }
215
+ specify { expect(subject.import(scope, batch_size: 2, &data_comparer.curry[cities[2].id])).to eq(false) }
216
+ specify { expect(subject.import(scope, batch_size: 2, &data_comparer.curry[cities[4].id])).to eq(false) }
217
+ specify { expect(subject.import(scope, batch_size: 2, &data_comparer.curry[deleted[0].id])).to eq(true) }
218
+ specify { expect(subject.import(scope, batch_size: 2, &data_comparer.curry[deleted[2].id])).to eq(true) }
170
219
  end
171
220
 
172
221
  context 'objects' do
173
222
  specify { expect(subject.import(cities + deleted) { |data| true }).to eq(true) }
174
223
  specify { expect(subject.import(cities + deleted) { |data| false }).to eq(false) }
175
- specify { expect(subject.import(cities + deleted, batch_size: 2, &data_comparer.curry[cities[0..1]])).to eq(false) }
176
- specify { expect(subject.import(cities + deleted, batch_size: 2, &data_comparer.curry[cities[2..3]])).to eq(false) }
177
- specify { expect(subject.import(cities + deleted, batch_size: 2, &data_comparer.curry[cities[4..5]])).to eq(false) }
178
- specify { expect(subject.import(cities + deleted, batch_size: 2, &data_comparer.curry[deleted[0..1]])).to eq(false) }
179
- specify { expect(subject.import(cities + deleted, batch_size: 2, &data_comparer.curry[deleted[2..3]])).to eq(false) }
224
+ specify { expect(subject.import(cities + deleted, batch_size: 2, &data_comparer.curry[cities[0].id])).to eq(false) }
225
+ specify { expect(subject.import(cities + deleted, batch_size: 2, &data_comparer.curry[cities[2].id])).to eq(false) }
226
+ specify { expect(subject.import(cities + deleted, batch_size: 2, &data_comparer.curry[cities[4].id])).to eq(false) }
227
+ specify { expect(subject.import(cities + deleted, batch_size: 2, &data_comparer.curry[deleted[0].id])).to eq(false) }
228
+ specify { expect(subject.import(cities + deleted, batch_size: 2, &data_comparer.curry[deleted[2].id])).to eq(false) }
180
229
  end
181
230
 
182
231
  context 'ids' do
183
232
  specify { expect(subject.import(ids) { |data| true }).to eq(true) }
184
233
  specify { expect(subject.import(ids) { |data| false }).to eq(false) }
185
- specify { expect(subject.import(ids, batch_size: 2, &data_comparer.curry[cities[0..1]])).to eq(false) }
186
- specify { expect(subject.import(ids, batch_size: 2, &data_comparer.curry[cities[2..3]])).to eq(false) }
187
- specify { expect(subject.import(ids, batch_size: 2, &data_comparer.curry[cities[4..5]])).to eq(false) }
188
- specify { expect(subject.import(ids, batch_size: 2, &data_comparer.curry[deleted[0..1]])).to eq(false) }
189
- specify { expect(subject.import(ids, batch_size: 2, &data_comparer.curry[deleted[2..3]])).to eq(false) }
234
+ specify { expect(subject.import(ids, batch_size: 2, &data_comparer.curry[cities[0].id])).to eq(false) }
235
+ specify { expect(subject.import(ids, batch_size: 2, &data_comparer.curry[cities[2].id])).to eq(false) }
236
+ specify { expect(subject.import(ids, batch_size: 2, &data_comparer.curry[cities[4].id])).to eq(false) }
237
+ specify { expect(subject.import(ids, batch_size: 2, &data_comparer.curry[deleted[0].id])).to eq(false) }
238
+ specify { expect(subject.import(ids, batch_size: 2, &data_comparer.curry[deleted[2].id])).to eq(false) }
190
239
  end
191
240
  end
192
241
  end
@@ -29,6 +29,13 @@ describe Chewy::Type::Adapter::Object do
29
29
  end
30
30
  end
31
31
 
32
+ describe '#identify' do
33
+ let!(:objects) { 3.times.map { double } }
34
+
35
+ specify { expect(subject.identify(objects)).to eq(objects) }
36
+ specify { expect(subject.identify(objects.first)).to eq([objects.first]) }
37
+ end
38
+
32
39
  describe '#import' do
33
40
  def import(*args)
34
41
  result = []
@@ -45,6 +52,8 @@ describe Chewy::Type::Adapter::Object do
45
52
  subject { described_class.new('product') }
46
53
 
47
54
  specify { expect(import).to eq([]) }
55
+ specify { expect(import nil).to eq([]) }
56
+
48
57
  specify { expect(import(objects)).to eq([{index: objects}]) }
49
58
  specify { expect(import(objects, batch_size: 2))
50
59
  .to eq([{index: objects.first(2)}, {index: objects.last(1)}]) }
@@ -56,39 +65,45 @@ describe Chewy::Type::Adapter::Object do
56
65
 
57
66
  specify { expect(import(objects.first, nil)).to eq([{index: [objects.first]}]) }
58
67
 
59
- context do
60
- let(:deleted) { 2.times.map { |i| double(delete_from_index?: true, destroyed?: true) } }
61
- specify { expect(import(deleted)).to eq([{delete: deleted}]) }
62
- end
68
+ context 'initial data' do
69
+ subject { described_class.new ->{ objects } }
63
70
 
64
- context do
65
- let(:deleted) { 2.times.map { |i| double(delete_from_index?: true, destroyed?: false) } }
66
- specify { expect(import(deleted)).to eq([{delete: deleted}]) }
67
- end
71
+ specify { expect(import).to eq([{index: objects}]) }
72
+ specify { expect(import nil).to eq([]) }
68
73
 
74
+ specify { expect(import(objects[0..1])).to eq([{index: objects[0..1]}]) }
75
+ specify { expect(import(batch_size: 2))
76
+ .to eq([{index: objects.first(2)}, {index: objects.last(1)}]) }
77
+ end
69
78
 
70
79
  context do
71
- let(:deleted) { 2.times.map { |i| double(delete_from_index?: false, destroyed?: true) } }
72
- specify { expect(import(deleted)).to eq([{delete: deleted}]) }
80
+ let(:deleted) { [
81
+ double(delete_from_index?: true, destroyed?: true),
82
+ double(delete_from_index?: true, destroyed?: false),
83
+ double(delete_from_index?: false, destroyed?: true),
84
+ double(delete_from_index?: false, destroyed?: false)
85
+ ] }
86
+
87
+ specify { expect(import(deleted)).to eq([
88
+ { delete: deleted[0..2], index: deleted.last(1) }
89
+ ]) }
73
90
  end
74
91
 
75
92
  context do
76
- let(:deleted) { 2.times.map { |i| double(delete_from_index?: false, destroyed?: false) } }
77
- specify { expect(import(deleted)).to eq([{index: deleted}]) }
93
+ subject { described_class.new('product', delete_if: :delete?) }
94
+ let(:deleted) { [
95
+ double(delete?: true, destroyed?: true),
96
+ double(delete?: true, destroyed?: false),
97
+ double(delete?: false, destroyed?: true),
98
+ double(delete?: false, destroyed?: false)
99
+ ] }
100
+
101
+ specify { expect(import(deleted)).to eq([
102
+ { delete: deleted[0..2], index: deleted.last(1) }
103
+ ]) }
78
104
  end
79
105
  end
80
106
 
81
- context do
82
- let(:products) { 3.times.map { |i| double.tap { |product|
83
- allow(product).to receive(:is_a?).with(Product).and_return(true)
84
- } } }
85
- let(:non_product) { double }
86
- subject { described_class.new(Product) }
87
-
88
- specify { expect(import(products)).to eq([{index: products}]) }
89
- specify { expect { import(products, non_product) {} }.to raise_error }
90
- end
91
-
92
107
  context 'error handling' do
93
108
  let(:products) { 3.times.map { |i| double.tap { |product| allow(product).to receive_messages(rating: i.next) } } }
94
109
  let(:deleted) { 2.times.map { |i| double(destroyed?: true, rating: i + 4) } }
@@ -116,20 +131,22 @@ describe Chewy::Type::Adapter::Object do
116
131
  specify { expect(subject.load(objects)).to eq(objects) }
117
132
  end
118
133
 
119
- context do
120
- before { allow(Product).to receive(:wrap) { |object| allow(object).to receive_messages(wrapped?: true); object } }
121
- subject { described_class.new(Product) }
122
- let(:objects) { 3.times.map { |i| double(wrapped?: false) } }
134
+ [:wrap, :load_one].each do |load_method|
135
+ context do
136
+ before { allow(Product).to receive(load_method) { |object| allow(object).to receive_messages(wrapped?: true); object } }
137
+ subject { described_class.new(Product) }
138
+ let(:objects) { 3.times.map { |i| double(wrapped?: false) } }
123
139
 
124
- specify { expect(subject.load(objects)).to satisfy { |objects| objects.all?(&:wrapped?) } }
125
- end
140
+ specify { expect(subject.load(objects)).to satisfy { |objects| objects.all?(&:wrapped?) } }
141
+ end
126
142
 
127
- context do
128
- before { allow(Product).to receive(:wrap) { |object| nil } }
129
- subject { described_class.new(Product) }
130
- let(:objects) { 3.times.map { |i| double(wrapped?: false) } }
143
+ context do
144
+ before { allow(Product).to receive(load_method) { |object| nil } }
145
+ subject { described_class.new(Product) }
146
+ let(:objects) { 3.times.map { |i| double(wrapped?: false) } }
131
147
 
132
- specify { expect(subject.load(objects)).to satisfy { |objects| objects.all?(&:nil?) } }
148
+ specify { expect(subject.load(objects)).to satisfy { |objects| objects.all?(&:nil?) } }
149
+ end
133
150
  end
134
151
  end
135
152
  end
@@ -58,15 +58,15 @@ describe Chewy::Type::Import do
58
58
  specify do
59
59
  dummy_cities.first.destroy
60
60
 
61
- expect(CitiesIndex.client).to receive(:bulk).with(hash_including(
62
- body: [{delete: {_id: dummy_cities.first.id}}]
63
- ))
64
-
65
- expect(CitiesIndex.client).to receive(:bulk).with(hash_including(
66
- body: [{index: {_id: 2, data: {'name' => "name1"}}}, {index: {_id: 3, data: {'name' => "name2"}}}]
67
- ))
61
+ imported = []
62
+ allow(CitiesIndex.client).to receive(:bulk) { |params| imported << params[:body]; nil }
68
63
 
69
64
  city.import dummy_cities.map(&:id), batch_size: 2
65
+ expect(imported.flatten).to match_array([
66
+ {index: {_id: 2, data: {'name' => 'name1'}}},
67
+ {index: {_id: 3, data: {'name' => 'name2'}}},
68
+ {delete: {_id: dummy_cities.first.id}}
69
+ ])
70
70
  end
71
71
 
72
72
  specify do
@@ -130,7 +130,7 @@ describe Chewy::Type::Import do
130
130
  end
131
131
 
132
132
  dummy_cities.first.destroy
133
- city.import dummy_cities, batch_size: 1
133
+ city.import dummy_cities, batch_size: 2
134
134
  expect(outer_payload).to eq({type: CitiesIndex::City, import: {delete: 1, index: 2}})
135
135
  end
136
136
 
@@ -140,7 +140,7 @@ describe Chewy::Type::Import do
140
140
  outer_payload = payload
141
141
  end
142
142
 
143
- city.import dummy_cities, batch_size: 1
143
+ city.import dummy_cities, batch_size: 2
144
144
  expect(outer_payload).to eq({type: CitiesIndex::City, import: {index: 3}})
145
145
  end
146
146
 
@@ -159,12 +159,12 @@ describe Chewy::Type::Import do
159
159
  outer_payload = payload
160
160
  end
161
161
 
162
- city.import dummy_cities, batch_size: 1
162
+ city.import dummy_cities, batch_size: 2
163
163
  expect(outer_payload).to eq({
164
164
  type: CitiesIndex::City,
165
165
  errors: {
166
166
  index: {
167
- 'MapperParsingException[object mapping for [city] tried to parse as object, but got EOF, has a concrete value been provided to it?]' => ['1', '2', '3']
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']
168
168
  }
169
169
  },
170
170
  import: {index: 3}
@@ -199,7 +199,7 @@ describe Chewy::Type::Import do
199
199
 
200
200
  specify { expect(city.import(dummy_cities)).to eq(false) }
201
201
  specify { expect(city.import(dummy_cities.map(&:id))).to eq(false) }
202
- specify { expect(city.import(dummy_cities, batch_size: 1)).to eq(false) }
202
+ specify { expect(city.import(dummy_cities, batch_size: 2)).to eq(false) }
203
203
  end
204
204
  end
205
205
 
@@ -209,7 +209,7 @@ describe Chewy::Type::Import do
209
209
 
210
210
  before do
211
211
  stub_model(:country)
212
- stub_model(:city)
212
+ stub_model(:city) { belongs_to :country }
213
213
  end
214
214
 
215
215
  before do
@@ -279,15 +279,15 @@ describe Chewy::Type::Import do
279
279
  end
280
280
  end
281
281
  end
282
-
282
+
283
283
  context 'root id', :orm do
284
284
  let(:canada) { Country.create(id: 1, name: 'Canada', country_code: 'CA', rating: 4) }
285
285
  let(:country) { CountriesIndex::Country }
286
-
286
+
287
287
  before do
288
288
  stub_model(:country)
289
289
  end
290
-
290
+
291
291
  before do
292
292
  stub_index(:countries) do
293
293
  define_type Country do
@@ -298,8 +298,8 @@ describe Chewy::Type::Import do
298
298
  end
299
299
  end
300
300
  end
301
-
302
- specify { expect( country.import(canada) ).to eq(true) }
301
+
302
+ specify { expect(country.import(canada)).to eq(true) }
303
303
  specify { expect { country.import(canada) }.to update_index(country).and_reindex(canada.country_code) }
304
304
 
305
305
  specify do
@@ -309,7 +309,7 @@ describe Chewy::Type::Import do
309
309
 
310
310
  country.import canada
311
311
  end
312
-
312
+
313
313
  specify do
314
314
  canada.update_attributes(rating: 9)
315
315
 
@@ -319,7 +319,7 @@ describe Chewy::Type::Import do
319
319
 
320
320
  country.import canada
321
321
  end
322
-
322
+
323
323
  specify do
324
324
  canada.destroy
325
325
 
@@ -329,7 +329,7 @@ describe Chewy::Type::Import do
329
329
 
330
330
  country.import canada
331
331
  end
332
-
332
+
333
333
  end # END root id
334
334
  end
335
335