chewy 0.6.2 → 0.7.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 (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