deferring 0.0.1

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