deferring 0.0.1

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.
@@ -0,0 +1,5 @@
1
+ # encoding: UTF-8
2
+
3
+ module Deferring
4
+ VERSION = '0.0.1'
5
+ end
@@ -0,0 +1,536 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'deferred has-and-belongs-to-many associations' do
4
+
5
+ before :each do
6
+ Person.create!(name: 'Alice')
7
+ Person.create!(name: 'Bob')
8
+
9
+ Team.create!(name: 'Database Administration')
10
+ Team.create!(name: 'End-User Support')
11
+ Team.create!(name: 'Operations')
12
+ end
13
+
14
+ let(:bob) { Person.where(name: 'Bob').first }
15
+
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 }
19
+
20
+ describe 'deferring' do
21
+
22
+ it 'does not create a link until parent is saved' do
23
+ bob.teams << dba << support
24
+ expect{ bob.save! }.to change{ Person.find(bob.id).teams.size }.from(0).to(2)
25
+ end
26
+
27
+ it 'does not unlink until parent is saved' do
28
+ bob.team_ids = [dba.id, support.id, operations.id]
29
+ bob.save!
30
+
31
+ bob.teams.delete([
32
+ Team.find(dba.id),
33
+ Team.find(operations.id)
34
+ ])
35
+
36
+ expect{ bob.save }.to change{ Person.find(bob.id).teams.size }.from(3).to(1)
37
+ end
38
+
39
+ it 'does not create a link when parent is not valid' do
40
+ bob.name = nil # Person.name should be present, Person should not be saved.
41
+ bob.teams << dba
42
+
43
+ expect{ bob.save }.not_to change{ Person.find(bob.id).teams.size }
44
+ end
45
+
46
+ it 'replaces existing records when assigning a new set of records' do
47
+ bob.teams = [dba]
48
+
49
+ # A mistake was made, Bob belongs to Support and Operations instead.
50
+ bob.teams = [support, operations]
51
+
52
+ # The initial assignment of Bob to the DBA team did not get saved, so
53
+ # 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)
55
+ end
56
+
57
+ describe '#collection_singular_ids' do
58
+
59
+ it 'returns ids of saved & unsaved associated records' do
60
+ bob.teams = [dba, operations]
61
+ expect(bob.team_ids.size).to eq(2)
62
+ expect(bob.team_ids).to eq [dba.id, operations.id]
63
+
64
+ expect{ bob.save }.to change{ Person.find(bob.id).team_ids.size }.from(0).to(2)
65
+
66
+ expect(bob.team_ids.size).to eq(2)
67
+ expect(bob.team_ids).to eq [dba.id, operations.id]
68
+ end
69
+
70
+ end
71
+
72
+ describe '#collections_singular_ids=' do
73
+
74
+ it 'sets associated records' do
75
+ bob.team_ids = [dba.id, operations.id]
76
+ bob.save
77
+ expect(bob.teams).to eq [dba, operations]
78
+ expect(bob.team_ids).to eq [dba.id, operations.id]
79
+
80
+ bob.reload
81
+ expect(bob.teams).to eq [dba, operations]
82
+ expect(bob.team_ids).to eq [dba.id, operations.id]
83
+ end
84
+
85
+ it 'replace existing records when assigning a new set of ids of records' do
86
+ bob.teams = [dba]
87
+
88
+ # A mistake was made, Bob belongs to Support and Operations instead. The
89
+ # teams are assigned through the singular collection ids method. Note
90
+ # that, this also updates the teams association.
91
+ bob.team_ids = [support.id, operations.id]
92
+ expect(bob.teams.length).to eq(2)
93
+
94
+ expect{ bob.save }.to change{ Person.find(bob.id).teams.size }.from(0).to(2)
95
+ end
96
+
97
+ it 'clears empty values from the ids to be assigned' do
98
+ bob.team_ids = [dba.id, '']
99
+ expect(bob.teams.length).to eq(1)
100
+
101
+ expect{ bob.save }.to change{ Person.where(name: 'Bob').first.teams.size }.from(0).to(1)
102
+ end
103
+
104
+ describe '#collection_checked=' do
105
+
106
+ it 'set associated records' do
107
+ bob.teams_checked = [dba.id, operations.id]
108
+
109
+ expect{ bob.save }.to change{ Person.where(name: 'Bob').first.teams.size }.from(0).to(2)
110
+ end
111
+
112
+ end
113
+
114
+ end
115
+
116
+ end
117
+
118
+ describe 'validating' do
119
+
120
+ xit 'does not add duplicate values' do
121
+ pending 'uniq does not work correctly yet' do
122
+ dba = Team.first
123
+ dba.people = [Person.first, Person.find(2), Person.last]
124
+
125
+ dba.people.size.should eq 2
126
+ dba.person_ids.should eq [1,2]
127
+ end
128
+ end
129
+
130
+ end
131
+
132
+ describe 'preloading' do
133
+
134
+ before do
135
+ bob.teams << dba << support
136
+ bob.save!
137
+ end
138
+
139
+ if rails30 # old-style preload
140
+ it 'loads the association' do
141
+ person = Person.where(name: 'Bob').first
142
+ Person.send(:preload_associations, person, [:teams])
143
+ expect(person.teams.loaded?).to be_true
144
+ expect(person.team_ids).to eq [dba.id, support.id]
145
+ end
146
+ end
147
+
148
+ if rails32 || rails4
149
+ it 'loads the association when pre-loading' do
150
+ person = Person.preload(:teams).where(name: 'Bob').first
151
+ expect(person.teams.loaded?).to be_true
152
+ expect(person.team_ids).to eq [dba.id, support.id]
153
+ end
154
+
155
+ it 'loads the association when eager loading' do
156
+ person = Person.eager_load(:teams).where(name: 'Bob').first
157
+ expect(person.teams.loaded?).to be_true
158
+ expect(person.team_ids).to eq [dba.id, support.id]
159
+ end
160
+
161
+ it 'loads the association when joining' do
162
+ person = Person.includes(:teams).where(name: 'Bob').first
163
+ expect(person.teams.loaded?).to be_true
164
+ expect(person.team_ids).to eq [dba.id, support.id]
165
+ end
166
+ end
167
+
168
+ it 'does not load the association when using a regular query' do
169
+ person = Person.where(name: 'Bob').first
170
+ expect(person.teams.loaded?).to be_false
171
+ end
172
+
173
+ end
174
+
175
+ describe 'reloading' do
176
+
177
+ before do
178
+ bob.teams << operations
179
+ bob.save!
180
+ end
181
+
182
+ it 'throws away unsaved changes when reloading the parent' do
183
+ # Assign Bob to some teams, but reload Bob before saving. This should
184
+ # remove the unsaved teams from the list of teams Bob is assigned to.
185
+ bob.teams << dba << support
186
+ bob.reload
187
+
188
+ expect(bob.teams).to eq [operations]
189
+ expect(bob.teams.pending_creates).to be_empty
190
+ end
191
+
192
+ it 'throws away unsaved changes when reloading the association' do
193
+ # Assign Bob to some teams, but reload the association before saving Bob.
194
+ bob.teams << dba << support
195
+ bob.teams.reload
196
+
197
+ expect(bob.teams).to eq [operations]
198
+ expect(bob.teams.pending_creates).to be_empty
199
+ end
200
+
201
+ it 'loads changes saved on the other side of the association' do
202
+ # The DBA team will add Bob to their team, without him knowing it!
203
+ dba.people << bob
204
+
205
+ # Bob does not know about the fact that he has been added to the DBA team.
206
+ expect(bob.teams).to eq [operations]
207
+
208
+ # After resetting Bob, the teams are retrieved from the database and Bob
209
+ # finds out he is now also a team-member of team DBA!
210
+ bob.teams.reload
211
+ expect(bob.teams).to eq [operations, dba]
212
+ end
213
+ end
214
+
215
+ describe 'resetting' do
216
+
217
+ before do
218
+ bob.teams << operations
219
+ bob.save!
220
+ end
221
+
222
+ it 'throws away unsaved changes when resetting the association' do
223
+ # Assign Bob to some teams, but reset the association before saving Bob.
224
+ bob.teams << dba << support
225
+ bob.teams.reset
226
+
227
+ expect(bob.teams).to eq [operations]
228
+ expect(bob.teams.pending_creates).to be_empty
229
+ end
230
+
231
+ it 'loads changes saved on the other side of the association' do
232
+ # The DBA team will add Bob to their team, without him knowing it!
233
+ dba.people << bob
234
+
235
+ # Bob does not know about the fact that he has been added to the DBA team.
236
+ expect(bob.teams).to eq [operations]
237
+
238
+ # After resetting Bob, the teams are retrieved from the database and Bob
239
+ # finds out he is now also a team-member of team DBA!
240
+ bob.teams.reset
241
+ expect(bob.teams).to eq [operations, dba]
242
+ end
243
+
244
+ end
245
+
246
+ describe 'enumerable methods that conflict with ActiveRecord' do
247
+
248
+ describe '#select' do
249
+ before do
250
+ bob.teams << dba << support << operations
251
+ bob.save!
252
+ end
253
+
254
+ it 'selects specified columns directly from the database' do
255
+ teams = bob.teams.select('name')
256
+
257
+ expect(teams.map(&:name)).to eq ['Database Administration', 'End-User Support', 'Operations']
258
+ expect(teams.map(&:id)).to eq [nil, nil, nil]
259
+ end
260
+
261
+ it 'calls a block on the deferred associations' do
262
+ teams = bob.teams.select { |team| team.id == 1 }
263
+ expect(teams.map(&:id)).to eq [1]
264
+ end
265
+ end
266
+
267
+ describe 'find' do
268
+ # TODO: Write some tests.
269
+ end
270
+
271
+ describe 'first' do
272
+ # TODO: Write some tests.
273
+ end
274
+
275
+ end
276
+
277
+ describe 'pending creates & deletes (aka links and unlinks)' do
278
+
279
+ describe 'pending creates' do
280
+
281
+ it 'returns newly build records' do
282
+ bob.teams.build(name: 'Service Desk')
283
+ expect(bob.teams.pending_creates.size).to eq(1)
284
+ end
285
+
286
+ it 'does not return newly created records' do
287
+ bob.teams.create!(name: 'Service Desk')
288
+ expect(bob.teams.pending_creates).to be_empty
289
+ end
290
+
291
+ it 'returns associated records that need to be linked to parent' do
292
+ bob.teams = [dba]
293
+ expect(bob.teams.pending_creates).to eq [dba]
294
+ end
295
+
296
+ it 'does not return associated records that already have a link' do
297
+ bob.teams = [dba]
298
+ bob.save!
299
+
300
+ bob.teams << operations
301
+
302
+ expect(bob.teams.pending_creates).to_not include dba
303
+ expect(bob.teams.pending_creates).to include operations
304
+ end
305
+
306
+ it 'does not return associated records that are to be deleted' do
307
+ bob.teams = [dba, operations]
308
+ bob.save!
309
+ bob.teams.delete(dba)
310
+
311
+ expect(bob.teams.pending_creates).to be_empty
312
+ end
313
+
314
+ it 'does not return a record that has just been removed (and has not been saved)' do
315
+ bob.teams = [dba]
316
+ bob.save!
317
+
318
+ bob.teams.delete(dba)
319
+ bob.teams << dba
320
+
321
+ expect(bob.teams.pending_deletes).to be_empty
322
+ expect(bob.teams.pending_creates).to be_empty
323
+ end
324
+ end
325
+
326
+ describe 'pending deletes' do
327
+
328
+ it 'returns associated records that need to be unlinked from parent' do
329
+ bob.teams = [dba]
330
+ bob.save!
331
+ bob.teams.delete(dba)
332
+
333
+ expect(bob.teams.pending_deletes).to eq [dba]
334
+ end
335
+
336
+ it 'returns an empty array when no records are to be deleted' do
337
+ bob.teams = [dba]
338
+ bob.save!
339
+
340
+ expect(bob.teams.pending_deletes).to be_empty
341
+ end
342
+
343
+ it 'does not return a record that has just been added (and has not been saved)' do
344
+ bob.teams = [dba]
345
+ bob.teams.delete(dba)
346
+
347
+ expect(bob.teams.pending_deletes).to be_empty
348
+ expect(bob.teams.pending_creates).to be_empty
349
+ end
350
+
351
+ end
352
+
353
+ end
354
+
355
+ # TODO: Clean up tests.
356
+
357
+ describe 'active record api' do
358
+
359
+ # it 'should execute first on deferred association' do
360
+ # p = Person.first
361
+ # p.team_ids = [dba.id, support.id, operations.id]
362
+ # expect(p.teams.first).to eq(dba)
363
+ # p.save!
364
+ # expect(Person.first.teams.first).to eq(dba)
365
+ # end
366
+
367
+ # it 'should execute last on deferred association' do
368
+ # p = Person.first
369
+ # p.team_ids = [dba.id, support.id, operations.id]
370
+ # expect(p.teams.last).to eq(operations)
371
+ # p.save!
372
+ # expect(Person.first.teams.last).to eq(operations)
373
+ # end
374
+
375
+ it 'should build a new record' do
376
+ p = Person.first
377
+ p.teams.build(name: 'Service Desk')
378
+ expect(p.teams[0]).to be_new_record
379
+ end
380
+
381
+ it 'should build and save a new record' do
382
+ p = Person.first
383
+ p.teams.build(name: 'Service Desk')
384
+ expect(p.teams[0]).to be_new_record
385
+ expect(Person.first.teams.size).to eq(0)
386
+ p.save
387
+ expect(Person.first.teams.size).to eq(1)
388
+ end
389
+
390
+ it 'should add a new record' do
391
+ p = Person.first
392
+ p.teams.create!(:name => 'Service Desk')
393
+ expect(p.teams[0]).to_not be_new_record
394
+ expect(Person.first.teams.size).to eq(1)
395
+ p.save
396
+ expect(Person.first.teams.size).to eq(1)
397
+ end
398
+
399
+ it 'should allow ActiveRecord::QueryMethods' do
400
+ p = Person.first
401
+ p.teams << dba << operations
402
+ p.save
403
+ expect(Person.first.teams.where(name: 'Operations').first).to eq(operations)
404
+ end
405
+
406
+ it 'should find one without loading collection' do
407
+ p = Person.first
408
+ p.teams = [Team.first, Team.find(3)]
409
+ p.save
410
+ teams = Person.first.teams
411
+ teams.loaded?.should == false
412
+ teams.find(3).should == Team.find(3)
413
+ teams.first.should == Team.first
414
+ teams.last.should == Team.find(3)
415
+ teams.loaded?.should == false
416
+ end
417
+
418
+ end
419
+
420
+ it 'should call before_add, after_add, before_remove, after_remove callbacks' do
421
+ bob = Person.first
422
+ bob.teams = [Team.first, Team.find(3)]
423
+ bob.save!
424
+
425
+ bob = Person.first
426
+ bob.teams.delete(bob.teams[0])
427
+ bob.teams << Team.find(2)
428
+ bob.save!
429
+
430
+ bob.audit_log.length.should == 4
431
+ bob.audit_log.should == [
432
+ 'Before removing team 1',
433
+ 'After removing team 1',
434
+ 'Before adding team 2',
435
+ 'After adding team 2'
436
+ ]
437
+ end
438
+
439
+ describe 'accepts_nested_attributes' do
440
+ # TODO: Write more tests.
441
+ it 'should mass assign' do
442
+ p = Person.first
443
+ p.teams << Team.first << Team.last << Team.find(2)
444
+ p.save
445
+
446
+ # Destroy team 2 and 3. Keep team 1.
447
+ p = Person.first
448
+ p.attributes = {
449
+ teams_attributes: [
450
+ { id: 1 },
451
+ { id: 3, _destroy: true },
452
+ { id: 2, _destroy: true }
453
+ ]
454
+ }
455
+ p.teams.length.should == 1
456
+ p.team_ids.sort.should == [1]
457
+
458
+ Person.first
459
+ Person.first.teams.length.should == 3
460
+ Person.first.team_ids.sort.should == [1,2,3]
461
+
462
+ p.save!
463
+
464
+ p = Person.first
465
+ p.teams.length.should == 1
466
+ p.team_ids.sort.should == [1]
467
+ end
468
+
469
+ it 'should mass assign' do
470
+ p = Person.first
471
+ p.teams << Team.first << Team.last << Team.find(2)
472
+ p.save
473
+
474
+ # Destroy team 2 and 3. Keep team 1.
475
+ p = Person.first
476
+ p.teams_attributes = [
477
+ { id: 1 },
478
+ { id: 3, _destroy: true },
479
+ { id: 2, _destroy: true }
480
+ ]
481
+ p.teams.length.should == 1
482
+ p.team_ids.sort.should == [1]
483
+
484
+ Person.first
485
+ Person.first.teams.length.should == 3
486
+ Person.first.team_ids.sort.should == [1,2,3]
487
+
488
+ p.save!
489
+
490
+ p = Person.first
491
+ p.teams.length.should == 1
492
+ p.team_ids.sort.should == [1]
493
+ end
494
+ end
495
+
496
+ describe 'validations' do
497
+
498
+ xit 'deferred habtm <=> regular habtm' do
499
+ alice = Person.where(name: 'Alice').first
500
+ bob = Person.where(name: 'Bob').first
501
+
502
+ team = Team.first
503
+ team.people << alice << bob
504
+ team.save!
505
+
506
+ bob.reload
507
+ expect(bob.teams.size).to eq(1)
508
+
509
+ alice.reload
510
+ expect(alice.teams.size).to eq(1)
511
+
512
+ team.people.create!(name: 'Chuck')
513
+ expect(team).to_not be_valid
514
+
515
+ bob.reload
516
+ alice.reload
517
+
518
+ expect(bob).to_not be_valid
519
+ expect(alice).to_not be_valid
520
+
521
+ expect(bob.save).to be_false
522
+ expect(alice.save).to be_false
523
+ end
524
+
525
+ xit 'does not validate records when validate: false' do
526
+ pending 'validate: false does not work' do
527
+ alice = Person.where(name: 'Alice').first
528
+ alice.teams.build(name: nil)
529
+ alice.save!
530
+
531
+ expect(alice.teams.size).to eq 1
532
+ end
533
+ end
534
+ end
535
+
536
+ end