deferring 0.2.1 → 0.3.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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.travis.yml +16 -15
- data/Appraisals +8 -8
- data/LICENSE.txt +1 -1
- data/README.md +1 -1
- data/deferring.gemspec +1 -1
- data/gemfiles/rails_40.gemfile +1 -1
- data/gemfiles/rails_40.gemfile.lock +34 -30
- data/gemfiles/rails_41.gemfile +1 -1
- data/gemfiles/rails_41.gemfile.lock +35 -31
- data/gemfiles/{rails_30.gemfile → rails_42.gemfile} +1 -1
- data/gemfiles/rails_42.gemfile.lock +64 -0
- data/gemfiles/{rails_32.gemfile → rails_50.gemfile} +1 -1
- data/gemfiles/rails_50.gemfile.lock +63 -0
- data/lib/deferring.rb +55 -52
- data/lib/deferring/deferred_association.rb +8 -6
- data/lib/deferring/deferred_callback_listener.rb +9 -3
- data/lib/deferring/version.rb +1 -3
- data/spec/lib/deferring_habtm_spec.rb +140 -90
- data/spec/lib/deferring_has_many_spec.rb +58 -44
- data/spec/spec_helper.rb +12 -1
- data/spec/support/active_record.rb +5 -5
- metadata +10 -11
- data/gemfiles/rails_30.gemfile.lock +0 -55
- data/gemfiles/rails_32.gemfile.lock +0 -57
- data/spec/support/rails_versions.rb +0 -13
data/lib/deferring.rb
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
# encoding: UTF-8
|
2
|
-
|
3
1
|
require 'deferring/version'
|
4
2
|
require 'deferring/deferred_association'
|
5
3
|
require 'deferring/deferred_callback_listener'
|
@@ -86,8 +84,10 @@ module Deferring
|
|
86
84
|
|
87
85
|
# TODO: Implement value_to_boolean code from rails for checking _destroy field.
|
88
86
|
if attributes['_destroy'] == '1' && options[:allow_destroy]
|
89
|
-
# remove from existing records
|
90
|
-
|
87
|
+
# remove from existing records and mark for destruction upon
|
88
|
+
# saving
|
89
|
+
send(:"#{association_name}").delete(existing_record)
|
90
|
+
existing_record.mark_for_destruction
|
91
91
|
end
|
92
92
|
end
|
93
93
|
|
@@ -127,34 +127,49 @@ module Deferring
|
|
127
127
|
define_method :deferred_unassignable_keys do
|
128
128
|
%w(_destroy id)
|
129
129
|
end
|
130
|
-
|
131
|
-
generate_find_or_create_deferred_association_method
|
132
130
|
end
|
133
131
|
|
134
132
|
private
|
135
133
|
|
136
134
|
def generate_deferred_association_methods(association_name, listeners, options = {})
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
135
|
+
deferred_association_name = :"deferred_#{association_name}"
|
136
|
+
inverse_association_name = options[:inverse_association_name]
|
137
|
+
autosave = options.fetch(:autosave, true)
|
138
|
+
type = options.fetch(:type)
|
139
|
+
validate = options.fetch(:validate, true)
|
140
|
+
dependent = options[:dependent]
|
142
141
|
|
143
142
|
# Store the original accessor methods of the association.
|
144
143
|
alias_method :"original_#{association_name}", :"#{association_name}"
|
145
144
|
alias_method :"original_#{association_name}=", :"#{association_name}="
|
146
145
|
|
147
146
|
# Accessor for our own association.
|
148
|
-
|
147
|
+
define_method(deferred_association_name) do
|
148
|
+
return instance_variable_get(:"@#{deferred_association_name}") if instance_variable_defined?(:"@#{deferred_association_name}")
|
149
|
+
|
150
|
+
klass = self.class.reflect_on_association(:"#{association_name}").klass
|
151
|
+
deferred_association = DeferredAssociation.new(send(:"original_#{association_name}"),
|
152
|
+
klass,
|
153
|
+
self,
|
154
|
+
inverse_association_name,
|
155
|
+
dependent)
|
156
|
+
listeners.each do |event_name, callback_method|
|
157
|
+
deferred_association.add_callback_listener(event_name, callback_method)
|
158
|
+
end
|
159
|
+
|
160
|
+
instance_variable_set(:"@#{deferred_association_name}", deferred_association)
|
161
|
+
end
|
162
|
+
|
163
|
+
define_method :"#{deferred_association_name}=" do |deferred_association|
|
164
|
+
instance_variable_set(:"@#{deferred_association_name}", deferred_association)
|
165
|
+
end
|
149
166
|
|
150
167
|
# collection
|
151
168
|
#
|
152
169
|
# Returns an array of all the associated objects. An empty array is returned
|
153
170
|
# if none are found.
|
154
|
-
# TODO: add force_reload argument?
|
155
171
|
define_method :"#{association_name}" do
|
156
|
-
|
157
|
-
send(:"deferred_#{association_name}")
|
172
|
+
send(deferred_association_name)
|
158
173
|
end
|
159
174
|
|
160
175
|
# collection=objects
|
@@ -162,8 +177,7 @@ module Deferring
|
|
162
177
|
# Replaces the collection's content by deleting and adding objects as
|
163
178
|
# appropriate.
|
164
179
|
define_method :"#{association_name}=" do |objects|
|
165
|
-
|
166
|
-
send(:"deferred_#{association_name}").objects = objects
|
180
|
+
send(deferred_association_name).objects = objects
|
167
181
|
end
|
168
182
|
|
169
183
|
# collection_singular_ids=
|
@@ -171,21 +185,18 @@ module Deferring
|
|
171
185
|
# Replace the collection by the objects identified by the primary keys in
|
172
186
|
# ids.
|
173
187
|
define_method :"#{association_name.singularize}_ids=" do |ids|
|
174
|
-
find_or_create_deferred_association(association_name, listeners, inverse_association_name, dependent)
|
175
|
-
|
176
188
|
ids ||= []
|
177
189
|
klass = self.class.reflect_on_association(:"#{association_name}").klass
|
178
190
|
objects = klass.find(ids.reject(&:blank?))
|
179
191
|
|
180
|
-
send(
|
192
|
+
send(deferred_association_name).objects = objects
|
181
193
|
end
|
182
194
|
|
183
195
|
# collection_singular_ids
|
184
196
|
#
|
185
197
|
# Returns an array of the associated objects' ids.
|
186
198
|
define_method :"#{association_name.singularize}_ids" do
|
187
|
-
|
188
|
-
send(:"deferred_#{association_name}").ids
|
199
|
+
send(deferred_association_name).ids
|
189
200
|
end
|
190
201
|
|
191
202
|
# collection_singular_checked
|
@@ -196,22 +207,27 @@ module Deferring
|
|
196
207
|
send(:"#{association_name.singularize}_ids=", ids.split(','))
|
197
208
|
end
|
198
209
|
|
199
|
-
|
200
|
-
define_method
|
201
|
-
|
210
|
+
# changed_for_autosave?
|
211
|
+
define_method(:"changed_for_autosave_with_#{deferred_association_name}?") do
|
212
|
+
changed = send(:"changed_for_autosave_without_#{deferred_association_name}?")
|
213
|
+
changed || send(deferred_association_name).changed_for_autosave?
|
214
|
+
end
|
215
|
+
alias_method_chain :changed_for_autosave?, deferred_association_name
|
202
216
|
|
217
|
+
after_validation :"perform_#{deferred_association_name}_validation!"
|
218
|
+
define_method :"perform_#{deferred_association_name}_validation!" do
|
203
219
|
# Do not perform validations for HABTM associations as they are always
|
204
220
|
# validated by Rails upon saving.
|
205
221
|
return true if type == :habtm
|
206
222
|
|
207
223
|
# Do not perform validation when the association has not been loaded
|
208
224
|
# (performance improvement).
|
209
|
-
return true unless send(
|
225
|
+
return true unless send(deferred_association_name).loaded?
|
210
226
|
|
211
227
|
# Do not perform validations when validate: false.
|
212
228
|
return true if validate == false
|
213
229
|
|
214
|
-
all_records_valid = send(
|
230
|
+
all_records_valid = send(deferred_association_name).objects.inject(true) do |valid, record|
|
215
231
|
unless record.valid?
|
216
232
|
valid = false
|
217
233
|
if autosave
|
@@ -232,49 +248,37 @@ module Deferring
|
|
232
248
|
end
|
233
249
|
|
234
250
|
# the save after the parent object has been saved
|
235
|
-
after_save :"
|
236
|
-
define_method :"
|
237
|
-
find_or_create_deferred_association(association_name, listeners, inverse_association_name, dependent)
|
238
|
-
|
251
|
+
after_save :"perform_#{deferred_association_name}_save!"
|
252
|
+
define_method :"perform_#{deferred_association_name}_save!" do
|
239
253
|
# Send the objects of our delegated association to the original
|
240
254
|
# association and store the result.
|
241
|
-
|
255
|
+
deferred_association = send(deferred_association_name)
|
256
|
+
if deferred_association.send(:objects_loaded?)
|
257
|
+
send(:"original_#{association_name}=", deferred_association.objects)
|
258
|
+
end
|
242
259
|
|
243
260
|
# Store the new value of the association into our delegated association.
|
244
261
|
update_deferred_association(association_name, listeners, inverse_association_name, dependent)
|
245
262
|
end
|
246
263
|
|
247
|
-
define_method :"
|
248
|
-
|
249
|
-
|
250
|
-
send(:"reload_without_deferred_#{association_name}", *args).tap do
|
264
|
+
define_method :"reload_with_#{deferred_association_name}" do |*args|
|
265
|
+
send(:"reload_without_#{deferred_association_name}", *args).tap do
|
251
266
|
update_deferred_association(association_name, listeners, inverse_association_name, dependent)
|
252
267
|
end
|
253
268
|
end
|
254
|
-
alias_method_chain :reload,
|
269
|
+
alias_method_chain :reload, deferred_association_name
|
255
270
|
|
256
271
|
generate_update_deferred_assocation_method
|
257
|
-
generate_find_or_create_deferred_association_method
|
258
272
|
end
|
259
273
|
|
260
274
|
def generate_update_deferred_assocation_method
|
261
275
|
define_method :update_deferred_association do |name, listeners, inverse_association_name, dependent|
|
262
276
|
klass = self.class.reflect_on_association(:"#{name}").klass
|
263
|
-
send(
|
264
|
-
:"deferred_#{name}=",
|
265
|
-
DeferredAssociation.new(send(:"original_#{name}"), klass, self, inverse_association_name, dependent))
|
277
|
+
deferred_association = DeferredAssociation.new(send(:"original_#{name}"), klass, self, inverse_association_name, dependent)
|
266
278
|
listeners.each do |event_name, callback_method|
|
267
|
-
|
268
|
-
send(:"deferred_#{name}").add_callback_listener(l)
|
269
|
-
end
|
270
|
-
end
|
271
|
-
end
|
272
|
-
|
273
|
-
def generate_find_or_create_deferred_association_method
|
274
|
-
define_method :find_or_create_deferred_association do |name, listeners, inverse_association_name, dependent|
|
275
|
-
if send(:"deferred_#{name}").nil?
|
276
|
-
update_deferred_association(name, listeners, inverse_association_name, dependent)
|
279
|
+
deferred_association.add_callback_listener(event_name, callback_method)
|
277
280
|
end
|
281
|
+
send(:"deferred_#{name}=", deferred_association)
|
278
282
|
end
|
279
283
|
end
|
280
284
|
|
@@ -284,7 +288,6 @@ module Deferring
|
|
284
288
|
[event_name, callback_method] if callback_method
|
285
289
|
end.compact
|
286
290
|
end
|
287
|
-
|
288
291
|
end
|
289
292
|
|
290
293
|
ActiveRecord::Base.send(:extend, Deferring)
|
@@ -1,10 +1,7 @@
|
|
1
|
-
# encoding: UTF-8
|
2
|
-
|
3
1
|
require 'delegate'
|
4
2
|
|
5
3
|
module Deferring
|
6
4
|
class DeferredAssociation < SimpleDelegator
|
7
|
-
# TODO: Write tests for enumerable.
|
8
5
|
include Enumerable
|
9
6
|
|
10
7
|
attr_reader :load_state,
|
@@ -187,14 +184,19 @@ module Deferring
|
|
187
184
|
# Returns the associated records to which the links will be deleted after
|
188
185
|
# saving the parent of the assocation.
|
189
186
|
def unlinks
|
190
|
-
# TODO: Write test for it.
|
191
187
|
return [] unless objects_loaded?
|
192
188
|
original_objects - objects
|
193
189
|
end
|
194
190
|
alias_method :pending_deletes, :unlinks
|
195
191
|
|
196
|
-
|
197
|
-
|
192
|
+
# Returns true if there are links that will be created or deleted when
|
193
|
+
# saving the parent of the association.
|
194
|
+
def changed_for_autosave?
|
195
|
+
links.any? || unlinks.any?
|
196
|
+
end
|
197
|
+
|
198
|
+
def add_callback_listener(event_name, callback_method)
|
199
|
+
(@listeners ||= []) << DeferredCallbackListener.new(event_name, parent_record, callback_method)
|
198
200
|
end
|
199
201
|
|
200
202
|
private
|
@@ -1,11 +1,17 @@
|
|
1
1
|
module Deferring
|
2
|
-
class DeferredCallbackListener
|
2
|
+
class DeferredCallbackListener
|
3
|
+
attr_reader :event_name, :callee, :callback_method
|
4
|
+
|
5
|
+
def initialize(event_name, callee, callback_method)
|
6
|
+
@event_name = event_name
|
7
|
+
@callee = callee
|
8
|
+
@callback_method = callback_method
|
9
|
+
end
|
3
10
|
|
4
11
|
[:before_link, :before_unlink, :after_link, :after_unlink].each do |event_name|
|
5
12
|
define_method(event_name) do |record|
|
6
|
-
callee.public_send(
|
13
|
+
callee.public_send(callback_method, record)
|
7
14
|
end
|
8
15
|
end
|
9
|
-
|
10
16
|
end
|
11
17
|
end
|
data/lib/deferring/version.rb
CHANGED
@@ -2,26 +2,37 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
RSpec.describe 'deferred has_and_belongs_to_many associations' do
|
4
4
|
|
5
|
-
before
|
5
|
+
before(:example) do
|
6
6
|
Person.create!(name: 'Alice')
|
7
7
|
Person.create!(name: 'Bob')
|
8
8
|
|
9
9
|
Team.create!(name: 'Database Administration')
|
10
10
|
Team.create!(name: 'End-User Support')
|
11
11
|
Team.create!(name: 'Operations')
|
12
|
+
|
13
|
+
bob; dba; support; operations
|
12
14
|
end
|
13
15
|
|
14
|
-
|
16
|
+
def bob
|
17
|
+
@bob ||= Person.where(name: 'Bob').first
|
18
|
+
end
|
15
19
|
|
16
|
-
|
17
|
-
|
18
|
-
|
20
|
+
def dba
|
21
|
+
@dba ||= Team.where(name: 'Database Administration').first
|
22
|
+
end
|
19
23
|
|
20
|
-
|
24
|
+
def support
|
25
|
+
@support ||= Team.where(name: 'End-User Support').first
|
26
|
+
end
|
21
27
|
|
28
|
+
def operations
|
29
|
+
@operations ||= Team.where(name: 'Operations').first
|
30
|
+
end
|
31
|
+
|
32
|
+
describe 'deferring' do
|
22
33
|
it 'does not create a link until parent is saved' do
|
23
34
|
bob.teams << dba << support
|
24
|
-
expect{ bob.save! }.to change{ Person.find(bob.id).teams.size }.from(0).to(2)
|
35
|
+
expect { bob.save! }.to change { Person.find(bob.id).teams.size }.from(0).to(2)
|
25
36
|
end
|
26
37
|
|
27
38
|
it 'does not unlink until parent is saved' do
|
@@ -33,14 +44,14 @@ RSpec.describe 'deferred has_and_belongs_to_many associations' do
|
|
33
44
|
Team.find(operations.id)
|
34
45
|
])
|
35
46
|
|
36
|
-
expect{ bob.save }.to change{ Person.find(bob.id).teams.size }.from(3).to(1)
|
47
|
+
expect { bob.save }.to change { Person.find(bob.id).teams.size }.from(3).to(1)
|
37
48
|
end
|
38
49
|
|
39
50
|
it 'does not create a link when parent is not valid' do
|
40
51
|
bob.name = nil # Person.name should be present, Person should not be saved.
|
41
52
|
bob.teams << dba
|
42
53
|
|
43
|
-
expect{ bob.save }.not_to change{ Person.find(bob.id).teams.size }
|
54
|
+
expect { bob.save }.not_to change { Person.find(bob.id).teams.size }
|
44
55
|
end
|
45
56
|
|
46
57
|
it 'replaces existing records when assigning a new set of records' do
|
@@ -51,7 +62,7 @@ RSpec.describe 'deferred has_and_belongs_to_many associations' do
|
|
51
62
|
|
52
63
|
# The initial assignment of Bob to the DBA team did not get saved, so
|
53
64
|
# at this moment Bob is not assigned to any team in the database.
|
54
|
-
expect{ bob.save }.to change{ Person.find(bob.id).teams.size }.from(0).to(2)
|
65
|
+
expect { bob.save }.to change { Person.find(bob.id).teams.size }.from(0).to(2)
|
55
66
|
end
|
56
67
|
|
57
68
|
it 'drops nil records' do
|
@@ -68,23 +79,25 @@ RSpec.describe 'deferred has_and_belongs_to_many associations' do
|
|
68
79
|
expect(bob.teams).to be_empty
|
69
80
|
end
|
70
81
|
|
71
|
-
|
82
|
+
it 'does not load the deferred associations when saving the parent' do
|
83
|
+
_, queries = catch_queries { bob.save! }
|
84
|
+
expect(queries.size).to eq(0)
|
85
|
+
end
|
72
86
|
|
87
|
+
describe '#collection_singular_ids' do
|
73
88
|
it 'returns ids of saved & unsaved associated records' do
|
74
89
|
bob.teams = [dba, operations]
|
75
90
|
expect(bob.team_ids.size).to eq(2)
|
76
91
|
expect(bob.team_ids).to eq [dba.id, operations.id]
|
77
92
|
|
78
|
-
expect{ bob.save }.to change{ Person.find(bob.id).team_ids.size }.from(0).to(2)
|
93
|
+
expect { bob.save }.to change { Person.find(bob.id).team_ids.size }.from(0).to(2)
|
79
94
|
|
80
95
|
expect(bob.team_ids.size).to eq(2)
|
81
96
|
expect(bob.team_ids).to eq [dba.id, operations.id]
|
82
97
|
end
|
83
|
-
|
84
98
|
end
|
85
99
|
|
86
100
|
describe '#collections_singular_ids=' do
|
87
|
-
|
88
101
|
it 'sets associated records' do
|
89
102
|
bob.team_ids = [dba.id, operations.id]
|
90
103
|
bob.save
|
@@ -105,14 +118,14 @@ RSpec.describe 'deferred has_and_belongs_to_many associations' do
|
|
105
118
|
bob.team_ids = [support.id, operations.id]
|
106
119
|
expect(bob.teams.length).to eq(2)
|
107
120
|
|
108
|
-
expect{ bob.save }.to change{ Person.find(bob.id).teams.size }.from(0).to(2)
|
121
|
+
expect { bob.save }.to change { Person.find(bob.id).teams.size }.from(0).to(2)
|
109
122
|
end
|
110
123
|
|
111
124
|
it 'clears empty values from the ids to be assigned' do
|
112
125
|
bob.team_ids = [dba.id, '']
|
113
126
|
expect(bob.teams.length).to eq(1)
|
114
127
|
|
115
|
-
expect{ bob.save }.to change{ Person.where(name: 'Bob').first.teams.size }.from(0).to(1)
|
128
|
+
expect { bob.save }.to change { Person.where(name: 'Bob').first.teams.size }.from(0).to(1)
|
116
129
|
end
|
117
130
|
|
118
131
|
it 'unlinks all records when assigning empty array' do
|
@@ -122,7 +135,7 @@ RSpec.describe 'deferred has_and_belongs_to_many associations' do
|
|
122
135
|
bob.team_ids = []
|
123
136
|
expect(bob.teams.length).to eq(0)
|
124
137
|
|
125
|
-
expect{ bob.save }.to change{ Person.where(name: 'Bob').first.teams.size }.from(2).to(0)
|
138
|
+
expect { bob.save }.to change { Person.where(name: 'Bob').first.teams.size }.from(2).to(0)
|
126
139
|
end
|
127
140
|
|
128
141
|
it 'unlinks all records when assigning nil' do
|
@@ -132,15 +145,14 @@ RSpec.describe 'deferred has_and_belongs_to_many associations' do
|
|
132
145
|
bob.team_ids = nil
|
133
146
|
expect(bob.teams.length).to eq(0)
|
134
147
|
|
135
|
-
expect{ bob.save }.to change{ Person.where(name: 'Bob').first.teams.size }.from(2).to(0)
|
148
|
+
expect { bob.save }.to change { Person.where(name: 'Bob').first.teams.size }.from(2).to(0)
|
136
149
|
end
|
137
150
|
end
|
138
151
|
|
139
152
|
describe '#collection_checked=' do
|
140
|
-
|
141
153
|
it 'set associated records' do
|
142
154
|
bob.teams_checked = "#{dba.id},#{operations.id}"
|
143
|
-
expect{ bob.save }.to change{ Person.where(name: 'Bob').first.teams.size }.from(0).to(2)
|
155
|
+
expect { bob.save }.to change { Person.where(name: 'Bob').first.teams.size }.from(0).to(2)
|
144
156
|
end
|
145
157
|
|
146
158
|
it 'replace existing records when assigning a new set of ids of records' do
|
@@ -175,13 +187,50 @@ RSpec.describe 'deferred has_and_belongs_to_many associations' do
|
|
175
187
|
|
176
188
|
expect{ bob.save }.to change{ Person.where(name: 'Bob').first.teams.size }.from(2).to(0)
|
177
189
|
end
|
178
|
-
|
179
190
|
end
|
180
191
|
|
192
|
+
describe '#changed_for_autosave?' do
|
193
|
+
it 'return false if nothing has changed' do
|
194
|
+
changed, queries = catch_queries { bob.changed_for_autosave? }
|
195
|
+
expect(queries).to be_empty
|
196
|
+
expect(changed).to eq(false)
|
197
|
+
end
|
198
|
+
|
199
|
+
it 'does not query anything if the objects have not been loaded' do
|
200
|
+
bob.name = 'James'
|
201
|
+
changed, queries = catch_queries { bob.changed_for_autosave? }
|
202
|
+
expect(queries).to be_empty
|
203
|
+
expect(changed).to eq(true)
|
204
|
+
end
|
205
|
+
|
206
|
+
it 'returns true if there is a pending create' do
|
207
|
+
bob.teams = [dba]
|
208
|
+
changed, queries = catch_queries { bob.changed_for_autosave? }
|
209
|
+
expect(queries).to be_empty
|
210
|
+
expect(changed).to eq(true)
|
211
|
+
end
|
212
|
+
|
213
|
+
it 'returns true if there is a pending delete' do
|
214
|
+
bob.teams = [dba]
|
215
|
+
bob.save!
|
216
|
+
|
217
|
+
bob = Person.where(name: 'Bob').first
|
218
|
+
bob.teams.delete(dba)
|
219
|
+
changed, queries = catch_queries { bob.changed_for_autosave? }
|
220
|
+
expect(queries).to be_empty
|
221
|
+
expect(changed).to eq(true)
|
222
|
+
end
|
223
|
+
|
224
|
+
it 'does not perform any queries if the original association has not been loaded' do
|
225
|
+
bob.teams = [dba]
|
226
|
+
changed, queries = catch_queries { bob.changed_for_autosave? }
|
227
|
+
expect(queries).to be_empty
|
228
|
+
expect(changed).to eq(true)
|
229
|
+
end
|
230
|
+
end
|
181
231
|
end
|
182
232
|
|
183
233
|
describe 'validating' do
|
184
|
-
|
185
234
|
xit 'does not add duplicate values' do
|
186
235
|
pending 'uniq does not work correctly yet' do
|
187
236
|
dba = Team.first
|
@@ -228,54 +277,39 @@ RSpec.describe 'deferred has_and_belongs_to_many associations' do
|
|
228
277
|
expect(alice.teams.size).to eq 1
|
229
278
|
end
|
230
279
|
end
|
231
|
-
|
232
280
|
end
|
233
281
|
|
234
282
|
describe 'preloading' do
|
235
|
-
|
236
283
|
before do
|
237
284
|
bob.teams << dba << support
|
238
285
|
bob.save!
|
239
286
|
end
|
240
287
|
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
expect(person.teams.loaded?).to be_truthy
|
246
|
-
expect(person.team_ids).to eq [dba.id, support.id]
|
247
|
-
end
|
288
|
+
it 'loads the association when pre-loading' do
|
289
|
+
person = Person.preload(:teams).where(name: 'Bob').first
|
290
|
+
expect(person.teams.loaded?).to be_truthy
|
291
|
+
expect(person.team_ids).to eq [dba.id, support.id]
|
248
292
|
end
|
249
293
|
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
end
|
256
|
-
|
257
|
-
it 'loads the association when eager loading' do
|
258
|
-
person = Person.eager_load(:teams).where(name: 'Bob').first
|
259
|
-
expect(person.teams.loaded?).to be_truthy
|
260
|
-
expect(person.team_ids).to eq [dba.id, support.id]
|
261
|
-
end
|
294
|
+
it 'loads the association when eager loading' do
|
295
|
+
person = Person.eager_load(:teams).where(name: 'Bob').first
|
296
|
+
expect(person.teams.loaded?).to be_truthy
|
297
|
+
expect(person.team_ids).to eq [dba.id, support.id]
|
298
|
+
end
|
262
299
|
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
end
|
300
|
+
it 'loads the association when joining' do
|
301
|
+
person = Person.includes(:teams).where(name: 'Bob').first
|
302
|
+
expect(person.teams.loaded?).to be_truthy
|
303
|
+
expect(person.team_ids).to eq [dba.id, support.id]
|
268
304
|
end
|
269
305
|
|
270
306
|
it 'does not load the association when using a regular query' do
|
271
307
|
person = Person.where(name: 'Bob').first
|
272
308
|
expect(person.teams.loaded?).to be_falsey
|
273
309
|
end
|
274
|
-
|
275
310
|
end
|
276
311
|
|
277
312
|
describe 'reloading' do
|
278
|
-
|
279
313
|
before do
|
280
314
|
bob.teams << operations
|
281
315
|
bob.save!
|
@@ -315,7 +349,6 @@ RSpec.describe 'deferred has_and_belongs_to_many associations' do
|
|
315
349
|
end
|
316
350
|
|
317
351
|
describe 'resetting' do
|
318
|
-
|
319
352
|
before do
|
320
353
|
bob.teams << operations
|
321
354
|
bob.save!
|
@@ -342,11 +375,9 @@ RSpec.describe 'deferred has_and_belongs_to_many associations' do
|
|
342
375
|
bob.teams.reset
|
343
376
|
expect(bob.teams).to eq [operations, dba]
|
344
377
|
end
|
345
|
-
|
346
378
|
end
|
347
379
|
|
348
380
|
describe 'enumerable methods that conflict with ActiveRecord' do
|
349
|
-
|
350
381
|
describe '#select' do
|
351
382
|
before do
|
352
383
|
bob.teams << dba << support << operations
|
@@ -373,11 +404,9 @@ RSpec.describe 'deferred has_and_belongs_to_many associations' do
|
|
373
404
|
describe 'first' do
|
374
405
|
# TODO: Write some tests.
|
375
406
|
end
|
376
|
-
|
377
407
|
end
|
378
408
|
|
379
409
|
describe 'callbacks' do
|
380
|
-
|
381
410
|
before(:example) do
|
382
411
|
bob = Person.where(name: 'Bob').first
|
383
412
|
bob.teams = [Team.find(3)]
|
@@ -498,26 +527,23 @@ RSpec.describe 'deferred has_and_belongs_to_many associations' do
|
|
498
527
|
'Before adding new team', 'After adding team 4'
|
499
528
|
])
|
500
529
|
end
|
501
|
-
|
502
530
|
end
|
503
531
|
|
504
|
-
describe '
|
505
|
-
|
506
|
-
describe 'pending creates' do
|
507
|
-
|
532
|
+
describe 'links & unlinks (aka pending creates and deletes)' do
|
533
|
+
describe 'links' do
|
508
534
|
it 'returns newly build records' do
|
509
535
|
bob.teams.build(name: 'Service Desk')
|
510
|
-
expect(bob.teams.
|
536
|
+
expect(bob.teams.links.size).to eq(1)
|
511
537
|
end
|
512
538
|
|
513
539
|
it 'does not return newly created records' do
|
514
540
|
bob.teams.create!(name: 'Service Desk')
|
515
|
-
expect(bob.teams.
|
541
|
+
expect(bob.teams.links).to be_empty
|
516
542
|
end
|
517
543
|
|
518
544
|
it 'returns associated records that need to be linked to parent' do
|
519
545
|
bob.teams = [dba]
|
520
|
-
expect(bob.teams.
|
546
|
+
expect(bob.teams.links).to eq [dba]
|
521
547
|
end
|
522
548
|
|
523
549
|
it 'does not return associated records that already have a link' do
|
@@ -526,8 +552,8 @@ RSpec.describe 'deferred has_and_belongs_to_many associations' do
|
|
526
552
|
|
527
553
|
bob.teams << operations
|
528
554
|
|
529
|
-
expect(bob.teams.
|
530
|
-
expect(bob.teams.
|
555
|
+
expect(bob.teams.links).to_not include dba
|
556
|
+
expect(bob.teams.links).to include operations
|
531
557
|
end
|
532
558
|
|
533
559
|
it 'does not return associated records that are to be deleted' do
|
@@ -535,7 +561,7 @@ RSpec.describe 'deferred has_and_belongs_to_many associations' do
|
|
535
561
|
bob.save!
|
536
562
|
bob.teams.delete(dba)
|
537
563
|
|
538
|
-
expect(bob.teams.
|
564
|
+
expect(bob.teams.links).to be_empty
|
539
565
|
end
|
540
566
|
|
541
567
|
it 'does not return a record that has just been removed (and has not been saved)' do
|
@@ -545,42 +571,58 @@ RSpec.describe 'deferred has_and_belongs_to_many associations' do
|
|
545
571
|
bob.teams.delete(dba)
|
546
572
|
bob.teams << dba
|
547
573
|
|
548
|
-
expect(bob.teams.
|
549
|
-
expect(bob.teams.
|
574
|
+
expect(bob.teams.unlinks).to be_empty
|
575
|
+
expect(bob.teams.links).to be_empty
|
550
576
|
end
|
551
|
-
end
|
552
577
|
|
553
|
-
|
578
|
+
it 'does not load the objects if the original association has not been loaded' do
|
579
|
+
bob.teams = [dba, operations]
|
580
|
+
bob.save!
|
581
|
+
bob = Person.where(name: 'Bob').first
|
554
582
|
|
583
|
+
unlinks, queries = catch_queries { bob.teams.links }
|
584
|
+
expect(queries).to be_empty
|
585
|
+
expect(unlinks).to eq([])
|
586
|
+
end
|
587
|
+
end
|
588
|
+
|
589
|
+
describe 'unlinks' do
|
555
590
|
it 'returns associated records that need to be unlinked from parent' do
|
556
591
|
bob.teams = [dba]
|
557
592
|
bob.save!
|
558
593
|
bob.teams.delete(dba)
|
559
594
|
|
560
|
-
expect(bob.teams.
|
595
|
+
expect(bob.teams.unlinks).to eq [dba]
|
561
596
|
end
|
562
597
|
|
563
598
|
it 'returns an empty array when no records are to be deleted' do
|
564
599
|
bob.teams = [dba]
|
565
600
|
bob.save!
|
566
601
|
|
567
|
-
expect(bob.teams.
|
602
|
+
expect(bob.teams.unlinks).to be_empty
|
568
603
|
end
|
569
604
|
|
570
605
|
it 'does not return a record that has just been added (and has not been saved)' do
|
571
606
|
bob.teams = [dba]
|
572
607
|
bob.teams.delete(dba)
|
573
608
|
|
574
|
-
expect(bob.teams.
|
575
|
-
expect(bob.teams.
|
609
|
+
expect(bob.teams.unlinks).to be_empty
|
610
|
+
expect(bob.teams.links).to be_empty
|
576
611
|
end
|
577
612
|
|
578
|
-
|
613
|
+
it 'does not load the objects if the original association has not been loaded' do
|
614
|
+
bob.teams = [dba, operations]
|
615
|
+
bob.save!
|
616
|
+
bob = Person.where(name: 'Bob').first
|
579
617
|
|
618
|
+
unlinks, queries = catch_queries { bob.teams.unlinks }
|
619
|
+
expect(queries).to be_empty
|
620
|
+
expect(unlinks).to eq([])
|
621
|
+
end
|
622
|
+
end
|
580
623
|
end
|
581
624
|
|
582
625
|
describe 'active record api' do
|
583
|
-
|
584
626
|
# it 'should execute first on deferred association' do
|
585
627
|
# p = Person.first
|
586
628
|
# p.team_ids = [dba.id, support.id, operations.id]
|
@@ -598,7 +640,6 @@ RSpec.describe 'deferred has_and_belongs_to_many associations' do
|
|
598
640
|
# end
|
599
641
|
|
600
642
|
describe '#build' do
|
601
|
-
|
602
643
|
it 'builds a new record' do
|
603
644
|
p = Person.first
|
604
645
|
p.teams.build(name: 'Service Desk')
|
@@ -606,11 +647,9 @@ RSpec.describe 'deferred has_and_belongs_to_many associations' do
|
|
606
647
|
expect(p.teams[0]).to be_new_record
|
607
648
|
expect{ p.save }.to change{ Person.first.teams.count }.from(0).to(1)
|
608
649
|
end
|
609
|
-
|
610
650
|
end
|
611
651
|
|
612
652
|
describe '#create!' do
|
613
|
-
|
614
653
|
it 'should create a persisted record' do
|
615
654
|
p = Person.first
|
616
655
|
p.teams.create!(:name => 'Service Desk')
|
@@ -623,7 +662,6 @@ RSpec.describe 'deferred has_and_belongs_to_many associations' do
|
|
623
662
|
expect(Person.first.teams.size).to eq(1)
|
624
663
|
expect{ p.save }.to_not change{ Person.first.teams.count }
|
625
664
|
end
|
626
|
-
|
627
665
|
end
|
628
666
|
|
629
667
|
describe '#destroy' do
|
@@ -719,22 +757,34 @@ RSpec.describe 'deferred has_and_belongs_to_many associations' do
|
|
719
757
|
it 'should allow ActiveRecord::QueryMethods' do
|
720
758
|
p = Person.first
|
721
759
|
p.teams << dba << operations
|
722
|
-
p.save
|
760
|
+
p.save!
|
723
761
|
expect(Person.first.teams.where(name: 'Operations').first).to eq(operations)
|
724
762
|
end
|
725
763
|
|
726
764
|
it 'should find one without loading collection' do
|
727
765
|
p = Person.first
|
728
766
|
p.teams = [Team.first, Team.find(3)]
|
729
|
-
p.save
|
730
|
-
teams = Person.first.teams
|
731
|
-
expect(teams.loaded?).to eq(false)
|
732
|
-
expect(teams.find(3)).to eq(Team.find(3))
|
733
|
-
expect(teams.first).to eq(Team.first)
|
734
|
-
expect(teams.last).to eq(Team.find(3))
|
735
|
-
expect(teams.loaded?).to eq(false)
|
736
|
-
end
|
767
|
+
p.save!
|
737
768
|
|
738
|
-
|
769
|
+
p = Person.first
|
770
|
+
_, queries = catch_queries { p.teams }
|
771
|
+
expect(queries).to be_empty
|
772
|
+
expect(p.teams.loaded?).to eq(false)
|
739
773
|
|
774
|
+
team, queries = catch_queries { p.teams.find(3) }
|
775
|
+
expect(queries.size).to eq(1)
|
776
|
+
expect(team).to eq(Team.find(3))
|
777
|
+
expect(p.teams.loaded?).to eq(false)
|
778
|
+
|
779
|
+
team, queries = catch_queries { p.teams.first }
|
780
|
+
expect(queries.size).to eq(1)
|
781
|
+
expect(team).to eq(Team.first)
|
782
|
+
expect(p.teams.loaded?).to eq(false)
|
783
|
+
|
784
|
+
team, queries = catch_queries { p.teams.last }
|
785
|
+
expect(queries.size).to eq(1)
|
786
|
+
expect(team).to eq(Team.find(3))
|
787
|
+
expect(p.teams.loaded?).to eq(false)
|
788
|
+
end
|
789
|
+
end
|
740
790
|
end
|