chewy 0.8.2 → 0.8.3

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.
@@ -111,7 +111,7 @@ module Chewy
111
111
  #
112
112
  def field *args, &block
113
113
  options = args.extract_options!
114
- build_root unless root_object
114
+ build_root
115
115
 
116
116
  if args.size > 1
117
117
  args.map { |name| field(name, options) }
@@ -138,7 +138,7 @@ module Chewy
138
138
  # end
139
139
  def agg *args, &block
140
140
  options = args.extract_options!
141
- build_root unless root_object
141
+ build_root
142
142
  self._agg_defs = _agg_defs.merge(args.first => block)
143
143
  end
144
144
  alias_method :aggregation, :agg
@@ -164,9 +164,7 @@ module Chewy
164
164
  # template template42: {match: 'hello*', mapping: {type: 'object'}} # or even pass a template as is
165
165
  #
166
166
  def template *args
167
- build_root unless root_object
168
-
169
- root_object.dynamic_template *args
167
+ build_root.dynamic_template *args
170
168
  end
171
169
  alias_method :dynamic_template, :template
172
170
 
@@ -192,6 +190,7 @@ module Chewy
192
190
  end
193
191
 
194
192
  def build_root options = {}, &block
193
+ return root_object if root_object
195
194
  self.root_object = Chewy::Fields::Root.new(type_name, options)
196
195
  expand_nested(root_object, &block)
197
196
  @_current_field = root_object
@@ -1,3 +1,3 @@
1
1
  module Chewy
2
- VERSION = '0.8.2'
2
+ VERSION = '0.8.3'
3
3
  end
@@ -25,7 +25,7 @@ module Sequel
25
25
  def self.apply(model)
26
26
  model.instance_eval do
27
27
  include ActiveSupport::Callbacks
28
- define_callbacks :commit, :save, :destroy
28
+ define_callbacks :commit, :destroy_commit, :save, :destroy
29
29
  end
30
30
  end
31
31
 
@@ -38,6 +38,7 @@ module Sequel
38
38
 
39
39
  if Chewy.use_after_commit_callbacks
40
40
  set_callback(:commit, callback_options, &update_proc)
41
+ set_callback(:destroy_commit, callback_options, &update_proc)
41
42
  else
42
43
  set_callback(:save, callback_options, &update_proc)
43
44
  set_callback(:destroy, callback_options, &update_proc)
@@ -54,6 +55,12 @@ module Sequel
54
55
  end
55
56
  end
56
57
 
58
+ def after_destroy_commit
59
+ run_callbacks(:destroy_commit) do
60
+ super
61
+ end
62
+ end
63
+
57
64
  def after_save
58
65
  run_callbacks(:save) do
59
66
  super
@@ -138,13 +138,13 @@ describe Chewy::Query::Criteria do
138
138
  specify { expect(subject.tap { |c| c.update_request_options(opt1: 'hello') }
139
139
  .merge(criteria.tap { |c| c.update_request_options(opt2: 'hello') }).request_options).to include(opt1: 'hello', opt2: 'hello') }
140
140
  specify { expect(subject.tap { |c| c.update_facets(field1: 'hello') }
141
- .merge(criteria.tap { |c| c.update_facets(field1: 'hello') }).facets).to eq({field1: 'hello', field1: 'hello'}) }
141
+ .merge(criteria.tap { |c| c.update_facets(field1: 'hello') }).facets).to eq({field1: 'hello'}) }
142
142
  specify { expect(subject.tap { |c| c.update_script_fields(distance_m: {script: "doc['coordinates'].distanceInMiles(lat, lon)"}) }
143
143
  .merge(criteria.tap { |c| c.update_script_fields(distance_km: {script: "doc['coordinates'].distanceInKm(lat, lon)"}) }).script_fields).to eq({distance_m: {script: "doc['coordinates'].distanceInMiles(lat, lon)"}, distance_km: {script: "doc['coordinates'].distanceInKm(lat, lon)"}}) }
144
144
  specify { expect(subject.tap { |c| c.update_scores(script: 'hello') }
145
145
  .merge(criteria.tap { |c| c.update_scores(script: 'foobar') }).scores).to eq([{script: 'hello'}, { script: 'foobar' } ]) }
146
146
  specify { expect(subject.tap { |c| c.update_aggregations(field1: 'hello') }
147
- .merge(criteria.tap { |c| c.update_aggregations(field1: 'hello') }).aggregations).to eq({field1: 'hello', field1: 'hello'}) }
147
+ .merge(criteria.tap { |c| c.update_aggregations(field1: 'hello') }).aggregations).to eq({field1: 'hello'}) }
148
148
  specify { expect(subject.tap { |c| c.update_queries(field1: 'hello') }
149
149
  .merge(criteria.tap { |c| c.update_queries(field2: 'hello') }).queries).to eq([{field1: 'hello'}, {field2: 'hello'}]) }
150
150
  specify { expect(subject.tap { |c| c.update_filters(field1: 'hello') }
@@ -170,11 +170,11 @@ describe Chewy::Query::Criteria do
170
170
  specify { expect(subject.tap { |c| c.update_request_options(opt1: 'hello') }
171
171
  .merge!(criteria.tap { |c| c.update_request_options(opt2: 'hello') }).request_options).to include(opt1: 'hello', opt2: 'hello') }
172
172
  specify { expect(subject.tap { |c| c.update_facets(field1: 'hello') }
173
- .merge!(criteria.tap { |c| c.update_facets(field1: 'hello') }).facets).to eq({field1: 'hello', field1: 'hello'}) }
173
+ .merge!(criteria.tap { |c| c.update_facets(field1: 'hello') }).facets).to eq({field1: 'hello'}) }
174
174
  specify { expect(subject.tap { |c| c.update_script_fields(distance_m: {script: "doc['coordinates'].distanceInMiles(lat, lon)"}) }
175
175
  .merge(criteria.tap { |c| c.update_script_fields(distance_km: {script: "doc['coordinates'].distanceInKm(lat, lon)"}) }).script_fields).to eq({distance_m: {script: "doc['coordinates'].distanceInMiles(lat, lon)"}, distance_km: {script: "doc['coordinates'].distanceInKm(lat, lon)"}}) }
176
176
  specify { expect(subject.tap { |c| c.update_aggregations(field1: 'hello') }
177
- .merge!(criteria.tap { |c| c.update_aggregations(field1: 'hello') }).aggregations).to eq({field1: 'hello', field1: 'hello'}) }
177
+ .merge!(criteria.tap { |c| c.update_aggregations(field1: 'hello') }).aggregations).to eq({field1: 'hello'}) }
178
178
  specify { expect(subject.tap { |c| c.update_queries(field1: 'hello') }
179
179
  .merge!(criteria.tap { |c| c.update_queries(field2: 'hello') }).queries).to eq([{field1: 'hello'}, {field2: 'hello'}]) }
180
180
  specify { expect(subject.tap { |c| c.update_filters(field1: 'hello') }
@@ -0,0 +1,59 @@
1
+ require 'spec_helper'
2
+
3
+ describe Chewy::Strategy::Atomic, :orm do
4
+ around { |example| Chewy.strategy(:bypass) { example.run } }
5
+
6
+ before do
7
+ stub_model(:country) do
8
+ update_index('countries#country') { self }
9
+ end
10
+
11
+ stub_index(:countries) do
12
+ define_type Country
13
+ end
14
+ end
15
+
16
+ let(:country) { Country.create!(name: 'hello', country_code: 'HL') }
17
+ let(:other_country) { Country.create!(name: 'world', country_code: 'WD') }
18
+
19
+ specify do
20
+ expect { [country, other_country].map(&:save!) }
21
+ .to update_index(CountriesIndex::Country, strategy: :atomic)
22
+ .and_reindex(country, other_country)
23
+ end
24
+
25
+ specify do
26
+ expect { [country, other_country].map(&:destroy) }
27
+ .to update_index(CountriesIndex::Country, strategy: :atomic)
28
+ .and_delete(country, other_country)
29
+ end
30
+
31
+ context do
32
+ before do
33
+ stub_index(:countries) do
34
+ define_type Country do
35
+ root id: -> { country_code } do
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ specify do
42
+ expect { [country, other_country].map(&:save!) }
43
+ .to update_index(CountriesIndex::Country, strategy: :atomic)
44
+ .and_reindex('HL', 'WD')
45
+ end
46
+
47
+ specify do
48
+ expect { [country, other_country].map(&:destroy) }
49
+ .to update_index(CountriesIndex::Country, strategy: :atomic)
50
+ .and_delete('HL', 'WD')
51
+ end
52
+
53
+ specify do
54
+ expect { country.save!; other_country.destroy }
55
+ .to update_index(CountriesIndex::Country, strategy: :atomic)
56
+ .and_reindex('HL').and_delete('WD')
57
+ end
58
+ end
59
+ end
@@ -38,8 +38,9 @@ describe Chewy::Type::Adapter::ActiveRecord, :active_record do
38
38
  end
39
39
 
40
40
  describe '#identify' do
41
+ subject { described_class.new(City) }
42
+
41
43
  context do
42
- subject { described_class.new(City) }
43
44
  let!(:cities) { 3.times.map { City.create! } }
44
45
 
45
46
  specify { expect(subject.identify(City.where(nil))).to match_array(cities.map(&:id)) }
@@ -50,7 +51,6 @@ describe Chewy::Type::Adapter::ActiveRecord, :active_record do
50
51
 
51
52
  context 'custom primary_key' do
52
53
  before { stub_model(:city) { self.primary_key = 'rating' } }
53
- subject { described_class.new(City) }
54
54
  let!(:cities) { 3.times.map { |i| City.create! { |c| c.rating = i } } }
55
55
 
56
56
  specify { expect(subject.identify(City.where(nil))).to match_array([0, 1, 2]) }
@@ -111,50 +111,26 @@ describe Chewy::Type::Adapter::ActiveRecord, :active_record do
111
111
  before { cities.last(2).map(&:destroy) }
112
112
  subject { described_class.new(City) }
113
113
 
114
- context do
115
- before do
116
- City.class_eval do
117
- def delete_from_index?
118
- rating.in?([1, 3])
119
- end
120
- end
121
- end
122
-
123
- specify { expect(import(City.where(nil))).to eq([
124
- { index: [cities[0]], delete: [cities[1]] }
125
- ]) }
126
- specify { expect(import(cities)).to eq([
127
- { index: [cities[0]], delete: [cities[1]] },
128
- { delete: cities.last(2) }
129
- ]) }
130
- specify { expect(import(cities.map(&:id))).to eq([
131
- { index: [cities[0]], delete: [cities[1]] },
132
- { delete: cities.last(2).map(&:id) }
133
- ]) }
134
- end
135
-
136
- context do
137
- before do
138
- City.class_eval do
139
- def delete_already?
140
- rating.in?([1, 3])
141
- end
114
+ before do
115
+ City.class_eval do
116
+ def delete_already?
117
+ rating.in?([1, 3])
142
118
  end
143
119
  end
144
- subject { described_class.new(City, delete_if: ->{ delete_already? }) }
145
-
146
- specify { expect(import(City.where(nil))).to eq([
147
- { index: [cities[0]], delete: [cities[1]] }
148
- ]) }
149
- specify { expect(import(cities)).to eq([
150
- { index: [cities[0]], delete: [cities[1]] },
151
- { delete: cities.last(2) }
152
- ]) }
153
- specify { expect(import(cities.map(&:id))).to eq([
154
- { index: [cities[0]], delete: [cities[1]] },
155
- { delete: cities.last(2).map(&:id) }
156
- ]) }
157
120
  end
121
+ subject { described_class.new(City, delete_if: ->{ delete_already? }) }
122
+
123
+ specify { expect(import(City.where(nil))).to eq([
124
+ { index: [cities[0]], delete: [cities[1]] }
125
+ ]) }
126
+ specify { expect(import(cities)).to eq([
127
+ { index: [cities[0]], delete: [cities[1]] },
128
+ { delete: cities.last(2) }
129
+ ]) }
130
+ specify { expect(import(cities.map(&:id))).to eq([
131
+ { index: [cities[0]], delete: [cities[1]] },
132
+ { delete: cities.last(2).map(&:id) }
133
+ ]) }
158
134
  end
159
135
 
160
136
  context 'custom primary_key' do
@@ -107,50 +107,26 @@ describe Chewy::Type::Adapter::Mongoid, :mongoid do
107
107
  before { cities.last(2).map(&:destroy) }
108
108
  subject { described_class.new(City) }
109
109
 
110
- context do
111
- before do
112
- City.class_eval do
113
- def delete_from_index?
114
- rating.in?([1, 3])
115
- end
110
+ before do
111
+ City.class_eval do
112
+ def delete_already?
113
+ rating.in?([1, 3])
116
114
  end
117
115
  end
118
-
119
- specify { expect(import(City.all)).to eq([
120
- { index: [cities[0]], delete: [cities[1]] }
121
- ]) }
122
- specify { expect(import(cities)).to eq([
123
- { index: [cities[0]], delete: [cities[1]] },
124
- { delete: cities.last(2) }
125
- ]) }
126
- specify { expect(import(cities.map(&:id))).to eq([
127
- { index: [cities[0]], delete: [cities[1]] },
128
- { delete: cities.last(2).map(&:id) }
129
- ]) }
130
- end
131
-
132
- context do
133
- before do
134
- City.class_eval do
135
- def delete_already?
136
- rating.in?([1, 3])
137
- end
138
- end
139
- end
140
- subject { described_class.new(City, delete_if: ->{ delete_already? }) }
141
-
142
- specify { expect(import(City.all)).to eq([
143
- { index: [cities[0]], delete: [cities[1]] }
144
- ]) }
145
- specify { expect(import(cities)).to eq([
146
- { index: [cities[0]], delete: [cities[1]] },
147
- { delete: cities.last(2) }
148
- ]) }
149
- specify { expect(import(cities.map(&:id))).to eq([
150
- { index: [cities[0]], delete: [cities[1]] },
151
- { delete: cities.last(2).map(&:id) }
152
- ]) }
153
116
  end
117
+ subject { described_class.new(City, delete_if: ->{ delete_already? }) }
118
+
119
+ specify { expect(import(City.all)).to eq([
120
+ { index: [cities[0]], delete: [cities[1]] }
121
+ ]) }
122
+ specify { expect(import(cities)).to eq([
123
+ { index: [cities[0]], delete: [cities[1]] },
124
+ { delete: cities.last(2) }
125
+ ]) }
126
+ specify { expect(import(cities.map(&:id))).to eq([
127
+ { index: [cities[0]], delete: [cities[1]] },
128
+ { delete: cities.last(2).map(&:id) }
129
+ ]) }
154
130
  end
155
131
 
156
132
  context 'default scope' do
@@ -76,19 +76,6 @@ describe Chewy::Type::Adapter::Object do
76
76
  .to eq([{index: objects.first(2)}, {index: objects.last(1)}]) }
77
77
  end
78
78
 
79
- context do
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
- ]) }
90
- end
91
-
92
79
  context do
93
80
  subject { described_class.new('product', delete_if: :delete?) }
94
81
  let(:deleted) { [
@@ -1,46 +1,320 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Chewy::Type::Adapter::Sequel, :sequel do
4
-
5
- let(:adapter) { described_class }
6
-
7
4
  before do
8
5
  stub_model(:city)
9
6
  stub_model(:country)
10
7
  end
11
8
 
12
9
  describe '#name' do
13
-
14
- it { expect( adapter.new(City).name ).to eq 'City' }
15
- it { expect( adapter.new(City.order(:id)).name ).to eq 'City' }
16
- it { expect( adapter.new(City, name: 'town').name ).to eq 'Town' }
10
+ it { expect( described_class.new(City).name ).to eq 'City' }
11
+ it { expect( described_class.new(City.order(:id)).name ).to eq 'City' }
12
+ it { expect( described_class.new(City, name: 'town').name ).to eq 'Town' }
17
13
 
18
14
  context do
19
15
  before { stub_model('namespace/city') }
20
16
 
21
- it { expect( adapter.new(Namespace::City).name ).to eq 'City' }
22
- it { expect( adapter.new(Namespace::City.order(:id)).name ).to eq 'City' }
17
+ it { expect( described_class.new(Namespace::City).name ).to eq 'City' }
18
+ it { expect( described_class.new(Namespace::City.order(:id)).name ).to eq 'City' }
23
19
  end
24
20
  end
25
21
 
26
- describe '#default_dataset' do
22
+ describe '#default_scope' do
23
+ it { expect( described_class.new(City).default_scope.sql ).to eql City.where(nil).sql }
24
+ it { expect( described_class.new(City.order(:id)).default_scope.sql ).to eql City.where(nil).sql }
25
+ it { expect( described_class.new(City.limit(10)).default_scope.sql ).to eql City.where(nil).sql }
26
+ it { expect( described_class.new(City.offset(10)).default_scope.sql ).to eql City.where(nil).sql }
27
+ it { expect( described_class.new(City.where(rating: 10)).default_scope.sql ).to eql City.where(rating: 10).sql }
28
+ end
29
+
30
+ describe '#type_name' do
31
+ specify { expect(described_class.new(City).type_name).to eq('city') }
32
+ specify { expect(described_class.new(City.order(:id)).type_name).to eq('city') }
33
+ specify { expect(described_class.new(City, name: 'town').type_name).to eq('town') }
34
+
35
+ context do
36
+ before { stub_model('namespace/city') }
27
37
 
28
- it { expect( adapter.new(City).default_dataset.sql ).to eql City.where(nil).sql }
29
- it { expect( adapter.new(City.order(:id)).default_dataset.sql ).to eql City.where(nil).sql }
30
- it { expect( adapter.new(City.limit(10)).default_dataset.sql ).to eql City.where(nil).sql }
31
- it { expect( adapter.new(City.offset(10)).default_dataset.sql ).to eql City.where(nil).sql }
32
- it { expect( adapter.new(City.where(rating: 10)).default_dataset.sql ).to eql City.where(rating: 10).sql }
38
+ specify { expect(described_class.new(Namespace::City).type_name).to eq('city') }
39
+ specify { expect(described_class.new(Namespace::City.order(:id)).type_name).to eq('city') }
40
+ end
33
41
  end
34
42
 
35
43
  describe '#identify' do
44
+ subject { described_class.new(City) }
45
+
46
+ context do
47
+ let!(:cities) { 3.times.map { City.new.save! } }
48
+
49
+ it { expect(subject.identify(City.where(nil)) ).to match_array cities.map(&:id) }
50
+ it { expect(subject.identify(cities) ).to eq cities.map(&:id) }
51
+ it { expect(subject.identify(cities.first) ).to eq([cities.first.id]) }
52
+ it { expect(subject.identify(cities.first(2).map(&:id)) ).to eq cities.first(2).map(&:id) }
53
+ end
54
+
55
+ context 'custom primary_key' do
56
+ before { stub_model(:city).set_dataset :rating_cities }
57
+ let!(:cities) { 3.times.map { |i| City.create! { |c| c.rating = i } } }
58
+
59
+ specify { expect(subject.identify(City.where(nil))).to match_array([0, 1, 2]) }
60
+ specify { expect(subject.identify(cities)).to eq([0, 1, 2]) }
61
+ specify { expect(subject.identify(cities.first)).to eq([0]) }
62
+ specify { expect(subject.identify(cities.first(2).map(&:rating))).to eq([0, 1]) }
63
+ end
64
+ end
65
+
66
+ describe '#import' do
67
+ def import(*args)
68
+ result = []
69
+ subject.import(*args) { |data| result.push data }
70
+ result
71
+ end
72
+
73
+ context do
74
+ let!(:cities) { 3.times.map { City.create! } }
75
+ let!(:deleted) { 4.times.map { City.create!.tap(&:destroy) } }
76
+ subject { described_class.new(City) }
77
+
78
+ specify { expect(import).to eq([{index: cities}]) }
79
+ specify { expect(import nil).to eq([]) }
80
+
81
+ specify { expect(import(City.order(:id))).to eq([{index: cities}]) }
82
+ specify { expect(import(City.order(:id), batch_size: 2))
83
+ .to eq([{index: cities.first(2)}, {index: cities.last(1)}]) }
84
+
85
+ specify { expect(import(cities)).to eq([{index: cities}]) }
86
+ specify { expect(import(cities, batch_size: 2))
87
+ .to eq([{index: cities.first(2)}, {index: cities.last(1)}]) }
88
+ specify { expect(import(cities, deleted))
89
+ .to eq([{index: cities}, {delete: deleted}]) }
90
+ specify { expect(import(cities, deleted, batch_size: 2)).to eq([
91
+ {index: cities.first(2)},
92
+ {index: cities.last(1)},
93
+ {delete: deleted.first(2)},
94
+ {delete: deleted.last(2)}]) }
95
+
96
+ specify { expect(import(cities.map(&:id))).to eq([{index: cities}]) }
97
+ specify { expect(import(deleted.map(&:id))).to eq([{delete: deleted.map(&:id)}]) }
98
+ specify { expect(import(cities.map(&:id), batch_size: 2))
99
+ .to eq([{index: cities.first(2)}, {index: cities.last(1)}]) }
100
+ specify { expect(import(cities.map(&:id), deleted.map(&:id)))
101
+ .to eq([{index: cities}, {delete: deleted.map(&:id)}]) }
102
+ specify { expect(import(cities.map(&:id), deleted.map(&:id), batch_size: 2)).to eq([
103
+ {index: cities.first(2)},
104
+ {index: cities.last(1)},
105
+ {delete: deleted.first(2).map(&:id)},
106
+ {delete: deleted.last(2).map(&:id)}]) }
107
+
108
+ specify { expect(import(cities.first, nil)).to eq([{index: [cities.first]}]) }
109
+ specify { expect(import(cities.first.id, nil)).to eq([{index: [cities.first]}]) }
110
+ end
111
+
112
+ context 'additional delete conitions' do
113
+ let!(:cities) { 4.times.map { |i| City.create! rating: i } }
114
+ before { cities.last(2).map(&:destroy) }
115
+ subject { described_class.new(City) }
116
+
117
+ before do
118
+ City.class_eval do
119
+ def delete_already?
120
+ rating.in?([1, 3])
121
+ end
122
+ end
123
+ end
124
+ subject { described_class.new(City, delete_if: ->{ delete_already? }) }
125
+
126
+ specify { expect(import(City.where(nil))).to eq([
127
+ { index: [cities[0]], delete: [cities[1]] }
128
+ ]) }
129
+ specify { expect(import(cities)).to eq([
130
+ { index: [cities[0]], delete: [cities[1]] },
131
+ { delete: cities.last(2) }
132
+ ]) }
133
+ specify { expect(import(cities.map(&:id))).to eq([
134
+ { index: [cities[0]], delete: [cities[1]] },
135
+ { delete: cities.last(2).map(&:id) }
136
+ ]) }
137
+ end
138
+
139
+ context 'custom primary_key' do
140
+ before { stub_model(:city).set_dataset :rating_cities }
141
+ let!(:cities) { 3.times.map { |i| City.create! { |c| c.rating = i + 7 } } }
142
+ let!(:deleted) { 3.times.map { |i| City.create! { |c| c.rating = i + 10 }.tap(&:destroy) } }
143
+ subject { described_class.new(City) }
144
+
145
+ specify { expect(import).to eq([{index: cities}]) }
146
+
147
+ specify { expect(import(City.order(:rating))).to eq([{index: cities}]) }
148
+ specify { expect(import(City.order(:rating), batch_size: 2))
149
+ .to eq([{index: cities.first(2)}, {index: cities.last(1)}]) }
150
+
151
+ specify { expect(import(cities)).to eq([{index: cities}]) }
152
+ specify { expect(import(cities, batch_size: 2))
153
+ .to eq([{index: cities.first(2)}, {index: cities.last(1)}]) }
154
+ specify { expect(import(cities, deleted))
155
+ .to eq([{index: cities}, {delete: deleted}]) }
156
+ specify { expect(import(cities, deleted, batch_size: 2)).to eq([
157
+ {index: cities.first(2)},
158
+ {index: cities.last(1)},
159
+ {delete: deleted.first(2)},
160
+ {delete: deleted.last(1)}]) }
161
+
162
+ specify { expect(import(cities.map(&:rating))).to eq([{index: cities}]) }
163
+ specify { expect(import(cities.map(&:rating), batch_size: 2))
164
+ .to eq([{index: cities.first(2)}, {index: cities.last(1)}]) }
165
+ specify { expect(import(cities.map(&:rating), deleted.map(&:rating)))
166
+ .to eq([{index: cities}, {delete: deleted.map(&:rating)}]) }
167
+ specify { expect(import(cities.map(&:rating), deleted.map(&:rating), batch_size: 2)).to eq([
168
+ {index: cities.first(2)},
169
+ {index: cities.last(1)},
170
+ {delete: deleted.first(2).map(&:rating)},
171
+ {delete: deleted.last(1).map(&:rating)}]) }
172
+ end
173
+
174
+ context 'default scope' do
175
+ let!(:cities) { 4.times.map { |i| City.create!(rating: i/3) } }
176
+ let!(:deleted) { 3.times.map { |i| City.create!.tap(&:destroy) } }
177
+ subject { described_class.new(City.where(rating: 0)) }
178
+
179
+ specify { expect(import).to eq([{index: cities.first(3)}]) }
180
+
181
+ specify { expect(import(City.where('rating < 2')))
182
+ .to eq([{index: cities.first(3)}]) }
183
+ specify { expect(import(City.where('rating < 2'), batch_size: 2))
184
+ .to eq([{index: cities.first(2)}, {index: [cities[2]]}]) }
185
+ specify { expect(import(City.where('rating < 1')))
186
+ .to eq([{index: cities.first(3)}]) }
187
+ specify { expect(import(City.where('rating > 1'))).to eq([]) }
188
+
189
+ specify { expect(import(cities.first(2)))
190
+ .to eq([{index: cities.first(2)}]) }
191
+ specify { expect(import(cities))
192
+ .to eq([{index: cities.first(3)}, {delete: cities.last(1)}]) }
193
+ specify { expect(import(cities, batch_size: 2))
194
+ .to eq([{index: cities.first(2)}, {index: [cities[2]]}, {delete: cities.last(1)}]) }
195
+ specify { expect(import(cities, deleted))
196
+ .to eq([{index: cities.first(3)}, {delete: cities.last(1) + deleted}]) }
197
+ specify { expect(import(cities, deleted, batch_size: 3)).to eq([
198
+ {index: cities.first(3)},
199
+ {delete: cities.last(1) + deleted.first(2)},
200
+ {delete: deleted.last(1)}]) }
201
+
202
+ specify { expect(import(cities.first(2).map(&:id)))
203
+ .to eq([{index: cities.first(2)}]) }
204
+ specify { expect(import(cities.map(&:id)))
205
+ .to eq([{index: cities.first(3)}, {delete: [cities.last.id]}]) }
206
+ specify { expect(import(cities.map(&:id), batch_size: 2))
207
+ .to eq([{index: cities.first(2)}, {index: [cities[2]]}, {delete: [cities.last.id]}]) }
208
+ specify { expect(import(cities.map(&:id), deleted.map(&:id)))
209
+ .to eq([{index: cities.first(3)}, {delete: [cities.last.id] + deleted.map(&:id)}]) }
210
+ specify { expect(import(cities.map(&:id), deleted.map(&:id), batch_size: 3)).to eq([
211
+ {index: cities.first(3)},
212
+ {delete: [cities.last.id] + deleted.first(2).map(&:id)},
213
+ {delete: deleted.last(1).map(&:id)}]) }
214
+ end
215
+
216
+ context 'error handling' do
217
+ let!(:cities) { 3.times.map { |i| City.create! } }
218
+ let!(:deleted) { 2.times.map { |i| City.create!.tap(&:destroy) } }
219
+ let(:ids) { (cities + deleted).map(&:id) }
220
+ subject { described_class.new(City) }
221
+
222
+ let(:data_comparer) do
223
+ ->(id, data) { objects = data[:index] || data[:delete]; !objects.map { |o| o.respond_to?(:id) ? o.id : o }.include?(id) }
224
+ end
225
+
226
+ context 'implicit scope' do
227
+ specify { expect(subject.import { |data| true }).to eq(true) }
228
+ specify { expect(subject.import { |data| false }).to eq(false) }
229
+ specify { expect(subject.import(batch_size: 1, &data_comparer.curry[cities[0].id])).to eq(false) }
230
+ specify { expect(subject.import(batch_size: 1, &data_comparer.curry[cities[1].id])).to eq(false) }
231
+ specify { expect(subject.import(batch_size: 1, &data_comparer.curry[cities[2].id])).to eq(false) }
232
+ specify { expect(subject.import(batch_size: 1, &data_comparer.curry[deleted[0].id])).to eq(true) }
233
+ specify { expect(subject.import(batch_size: 1, &data_comparer.curry[deleted[1].id])).to eq(true) }
234
+ end
235
+
236
+ context 'explicit scope' do
237
+ let(:scope) { City.where(id: ids) }
238
+
239
+ specify { expect(subject.import(scope) { |data| true }).to eq(true) }
240
+ specify { expect(subject.import(scope) { |data| false }).to eq(false) }
241
+ specify { expect(subject.import(scope, batch_size: 1, &data_comparer.curry[cities[0].id])).to eq(false) }
242
+ specify { expect(subject.import(scope, batch_size: 1, &data_comparer.curry[cities[1].id])).to eq(false) }
243
+ specify { expect(subject.import(scope, batch_size: 1, &data_comparer.curry[cities[2].id])).to eq(false) }
244
+ specify { expect(subject.import(scope, batch_size: 1, &data_comparer.curry[deleted[0].id])).to eq(true) }
245
+ specify { expect(subject.import(scope, batch_size: 1, &data_comparer.curry[deleted[1].id])).to eq(true) }
246
+ end
247
+
248
+ context 'objects' do
249
+ specify { expect(subject.import(cities + deleted) { |data| true }).to eq(true) }
250
+ specify { expect(subject.import(cities + deleted) { |data| false }).to eq(false) }
251
+ specify { expect(subject.import(cities + deleted, batch_size: 1, &data_comparer.curry[cities[0].id])).to eq(false) }
252
+ specify { expect(subject.import(cities + deleted, batch_size: 1, &data_comparer.curry[cities[1].id])).to eq(false) }
253
+ specify { expect(subject.import(cities + deleted, batch_size: 1, &data_comparer.curry[cities[2].id])).to eq(false) }
254
+ specify { expect(subject.import(cities + deleted, batch_size: 1, &data_comparer.curry[deleted[0].id])).to eq(false) }
255
+ specify { expect(subject.import(cities + deleted, batch_size: 1, &data_comparer.curry[deleted[1].id])).to eq(false) }
256
+ end
257
+
258
+ context 'ids' do
259
+ specify { expect(subject.import(ids) { |data| true }).to eq(true) }
260
+ specify { expect(subject.import(ids) { |data| false }).to eq(false) }
261
+ specify { expect(subject.import(ids, batch_size: 1, &data_comparer.curry[cities[0].id])).to eq(false) }
262
+ specify { expect(subject.import(ids, batch_size: 1, &data_comparer.curry[cities[1].id])).to eq(false) }
263
+ specify { expect(subject.import(ids, batch_size: 1, &data_comparer.curry[cities[2].id])).to eq(false) }
264
+ specify { expect(subject.import(ids, batch_size: 1, &data_comparer.curry[deleted[0].id])).to eq(false) }
265
+ specify { expect(subject.import(ids, batch_size: 1, &data_comparer.curry[deleted[1].id])).to eq(false) }
266
+ end
267
+ end
268
+ end
269
+
270
+ describe '#load' do
36
271
  context do
37
- subject(:s) { adapter.new(City) }
38
- let!(:cities) { 3.times.map { City.new.save } }
272
+ let!(:cities) { 3.times.map { |i| City.create!(rating: i/2) } }
273
+ let!(:deleted) { 2.times.map { |i| City.create!.tap(&:destroy) } }
274
+
275
+ let(:type) { double(type_name: 'user') }
276
+
277
+ subject { described_class.new(City) }
278
+
279
+ specify { expect(subject.load(cities.map { |c| double(id: c.id) }, _type: type)).to eq(cities) }
280
+ specify { expect(subject.load(cities.map { |c| double(id: c.id) }.reverse, _type: type)).to eq(cities.reverse) }
281
+ specify { expect(subject.load(deleted.map { |c| double(id: c.id) }, _type: type)).to eq([nil, nil]) }
282
+ specify { expect(subject.load((cities + deleted).map { |c| double(id: c.id) }, _type: type)).to eq([*cities, nil, nil]) }
283
+ specify { expect(subject.load(cities.map { |c| double(id: c.id) }, _type: type, scope: ->{ where(rating: 0) }))
284
+ .to eq(cities.first(2) + [nil]) }
285
+ specify { expect(subject.load(cities.map { |c| double(id: c.id) },
286
+ _type: type, scope: ->{ where(rating: 0) }, user: {scope: ->{ where(rating: 1)}}))
287
+ .to eq([nil, nil] + cities.last(1)) }
288
+ xspecify { expect(subject.load(cities.map { |c| double(id: c.id) }, _type: type, scope: City.where(rating: 1)))
289
+ .to eq([nil, nil] + cities.last(1)) }
290
+ specify { expect(subject.load(cities.map { |c| double(id: c.id) },
291
+ _type: type, scope: City.where(rating: 1), user: {scope: ->{ where(rating: 0)}}))
292
+ .to eq(cities.first(2) + [nil]) }
293
+ end
294
+
295
+ context 'custom primary_key' do
296
+ before { stub_model(:city).set_dataset :rating_cities }
297
+ let!(:cities) { 3.times.map { |i| City.create!(country_id: i/2) { |c| c.rating = i + 7 } } }
298
+ let!(:deleted) { 2.times.map { |i| City.create! { |c| c.rating = i + 10 }.tap(&:destroy) } }
299
+
300
+ let(:type) { double(type_name: 'user') }
301
+
302
+ subject { described_class.new(City) }
39
303
 
40
- it { expect( s.identify(City.where(nil)) ).to match_array cities.map(&:id) }
41
- it { expect( s.identify(cities) ).to eq cities.map(&:id) }
42
- it { expect( s.identify(cities.first) ).to eq([cities.first.id]) }
43
- it { expect( s.identify(cities.first(2).map(&:pk)) ).to eq cities.first(2).map(&:id) }
304
+ specify { expect(subject.load(cities.map { |c| double(rating: c.rating) }, _type: type)).to eq(cities) }
305
+ specify { expect(subject.load(cities.map { |c| double(rating: c.rating) }.reverse, _type: type)).to eq(cities.reverse) }
306
+ specify { expect(subject.load(deleted.map { |c| double(rating: c.rating) }, _type: type)).to eq([nil, nil]) }
307
+ specify { expect(subject.load((cities + deleted).map { |c| double(rating: c.rating) }, _type: type)).to eq([*cities, nil, nil]) }
308
+ specify { expect(subject.load(cities.map { |c| double(rating: c.rating) }, _type: type, scope: ->{ where(country_id: 0) }))
309
+ .to eq(cities.first(2) + [nil]) }
310
+ specify { expect(subject.load(cities.map { |c| double(rating: c.rating) },
311
+ _type: type, scope: ->{ where(country_id: 0) }, user: {scope: ->{ where(country_id: 1)}}))
312
+ .to eq([nil, nil] + cities.last(1)) }
313
+ xspecify { expect(subject.load(cities.map { |c| double(rating: c.rating) }, _type: type, scope: City.where(country_id: 1)))
314
+ .to eq([nil, nil] + cities.last(1)) }
315
+ specify { expect(subject.load(cities.map { |c| double(rating: c.rating) },
316
+ _type: type, scope: City.where(country_id: 1), user: {scope: ->{ where(country_id: 0)}}))
317
+ .to eq(cities.first(2) + [nil]) }
44
318
  end
45
319
  end
46
320
  end