chewy 0.8.2 → 0.8.3

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