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.
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
- send(:"#{association_name}").destroy(existing_record)
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
- inverse_association_name = options[:inverse_association_name]
138
- autosave = options.fetch(:autosave, true)
139
- type = options.fetch(:type)
140
- validate = options.fetch(:validate, true)
141
- dependent = options[:dependent]
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
- attr_accessor :"deferred_#{association_name}"
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
- find_or_create_deferred_association(association_name, listeners, inverse_association_name, dependent)
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
- find_or_create_deferred_association(association_name, listeners, inverse_association_name, dependent)
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(:"deferred_#{association_name}").objects = objects
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
- find_or_create_deferred_association(association_name, listeners, inverse_association_name, dependent)
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
- after_validation :"perform_deferred_#{association_name}_validation!"
200
- define_method :"perform_deferred_#{association_name}_validation!" do
201
- find_or_create_deferred_association(association_name, listeners, inverse_association_name, dependent)
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(:"deferred_#{association_name}").loaded?
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(:"deferred_#{association_name}").objects.inject(true) do |valid, record|
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 :"perform_deferred_#{association_name}_save!"
236
- define_method :"perform_deferred_#{association_name}_save!" do
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
- send(:"original_#{association_name}=", send(:"deferred_#{association_name}").objects)
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 :"reload_with_deferred_#{association_name}" do |*args|
248
- find_or_create_deferred_association(association_name, listeners, inverse_association_name, dependent)
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, :"deferred_#{association_name}"
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
- l = DeferredCallbackListener.new(event_name, self, callback_method)
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
- def add_callback_listener(listener)
197
- (@listeners ||= []) << listener
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 < Struct.new(:event_name, :callee, :callback)
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(callback, record)
13
+ callee.public_send(callback_method, record)
7
14
  end
8
15
  end
9
-
10
16
  end
11
17
  end
@@ -1,5 +1,3 @@
1
- # encoding: UTF-8
2
-
3
1
  module Deferring
4
- VERSION = '0.2.1'
2
+ VERSION = '0.3.0'
5
3
  end
@@ -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 :each do
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
- let(:bob) { Person.where(name: 'Bob').first }
16
+ def bob
17
+ @bob ||= Person.where(name: 'Bob').first
18
+ end
15
19
 
16
- let(:dba) { Team.where(name: 'Database Administration').first }
17
- let(:support) { Team.where(name: 'End-User Support').first }
18
- let(:operations) { Team.where(name: 'Operations').first }
20
+ def dba
21
+ @dba ||= Team.where(name: 'Database Administration').first
22
+ end
19
23
 
20
- describe 'deferring' do
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
- describe '#collection_singular_ids' do
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
- if rails30 # old-style preload
242
- it 'loads the association' do
243
- person = Person.where(name: 'Bob').first
244
- Person.send(:preload_associations, person, [:teams])
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
- if rails32 || rails4
251
- it 'loads the association when pre-loading' do
252
- person = Person.preload(:teams).where(name: 'Bob').first
253
- expect(person.teams.loaded?).to be_truthy
254
- expect(person.team_ids).to eq [dba.id, support.id]
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
- it 'loads the association when joining' do
264
- person = Person.includes(:teams).where(name: 'Bob').first
265
- expect(person.teams.loaded?).to be_truthy
266
- expect(person.team_ids).to eq [dba.id, support.id]
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 'pending creates & deletes (aka links and unlinks)' do
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.pending_creates.size).to eq(1)
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.pending_creates).to be_empty
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.pending_creates).to eq [dba]
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.pending_creates).to_not include dba
530
- expect(bob.teams.pending_creates).to include operations
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.pending_creates).to be_empty
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.pending_deletes).to be_empty
549
- expect(bob.teams.pending_creates).to be_empty
574
+ expect(bob.teams.unlinks).to be_empty
575
+ expect(bob.teams.links).to be_empty
550
576
  end
551
- end
552
577
 
553
- describe 'pending deletes' do
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.pending_deletes).to eq [dba]
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.pending_deletes).to be_empty
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.pending_deletes).to be_empty
575
- expect(bob.teams.pending_creates).to be_empty
609
+ expect(bob.teams.unlinks).to be_empty
610
+ expect(bob.teams.links).to be_empty
576
611
  end
577
612
 
578
- end
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
- end
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