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.
- data/.gitignore +18 -0
- data/.rspec +2 -0
- data/Appraisals +15 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +22 -0
- data/README.md +236 -0
- data/Rakefile +11 -0
- data/deferring.gemspec +31 -0
- data/gemfiles/rails_30.gemfile +7 -0
- data/gemfiles/rails_30.gemfile.lock +51 -0
- data/gemfiles/rails_32.gemfile +7 -0
- data/gemfiles/rails_32.gemfile.lock +53 -0
- data/gemfiles/rails_4.gemfile +7 -0
- data/gemfiles/rails_4.gemfile.lock +58 -0
- data/gemfiles/rails_40.gemfile +7 -0
- data/gemfiles/rails_40.gemfile.lock +59 -0
- data/gemfiles/rails_41.gemfile +7 -0
- data/gemfiles/rails_41.gemfile.lock +58 -0
- data/lib/deferring.rb +150 -0
- data/lib/deferring/deferred_association.rb +187 -0
- data/lib/deferring/version.rb +5 -0
- data/spec/lib/deferring_spec.rb +536 -0
- data/spec/spec_helper.rb +26 -0
- data/spec/support/active_record.rb +28 -0
- data/spec/support/models.rb +67 -0
- data/spec/support/rails_versions.rb +13 -0
- metadata +175 -0
@@ -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
|