m4dbi 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
data/spec/model.rb ADDED
@@ -0,0 +1,797 @@
1
+ require 'spec/helper'
2
+
3
+ # See test-schema.sql and test-data.sql
4
+
5
+ def reset_data( dbh = $dbh, datafile = "test-data.sql" )
6
+ dir = File.dirname( __FILE__ )
7
+ File.read( "#{dir}/#{datafile}" ).split( /;/ ).each do |command|
8
+ dbh.do( command )
9
+ end
10
+ end
11
+
12
+ describe 'DBI::Model' do
13
+ it 'raises an exception when trying to define a model before connecting to a database' do
14
+ dbh = DBI::DatabaseHandle.last_handle
15
+ if dbh and dbh.respond_to? :disconnect
16
+ dbh.disconnect
17
+ end
18
+ should.raise do
19
+ @m_author = Class.new( DBI::Model( :authors ) )
20
+ end
21
+ end
22
+ end
23
+
24
+ $dbh = DBI.connect( "DBI:Pg:m4dbi", "m4dbi", "m4dbi" )
25
+ reset_data
26
+
27
+ class ManyCol < DBI::Model( :many_col_table )
28
+ def inc
29
+ self.c1 = c1 + 10
30
+ end
31
+
32
+ def dec
33
+ self.c1 = c1 - 10
34
+ end
35
+ end
36
+
37
+ describe 'A DBI::Model subclass' do
38
+ before do
39
+ # Here we subclass DBI::Model.
40
+ # This is nearly equivalent to the typical "ChildClassName < ParentClassName"
41
+ # syntax, but allows us to refer to the class in our specs.
42
+ @m_author = Class.new( DBI::Model( :authors ) )
43
+ @m_post = Class.new( DBI::Model( :posts ) )
44
+ @m_empty = Class.new( DBI::Model( :empty_table ) )
45
+ @m_mc = Class.new( DBI::Model( :many_col_table ) )
46
+ class Author < DBI::Model( :authors ); end
47
+ end
48
+
49
+ it 'can be defined' do
50
+ @m_author.should.not.be.nil
51
+ @m_post.should.not.be.nil
52
+ end
53
+
54
+ it 'maintains identity across different inheritances' do
55
+ should.not.raise do
56
+ class Author < DBI::Model( :authors ); end
57
+ class Author < DBI::Model( :authors ); end
58
+ end
59
+ end
60
+
61
+ it 'maintains member methods across redefinitions' do
62
+ class Author < DBI::Model( :authors )
63
+ def method1; 1; end
64
+ end
65
+ class Author < DBI::Model( :authors )
66
+ def method2; 2; end
67
+ end
68
+ a = Author[ 3 ]
69
+ a.method1.should.equal 1
70
+ a.method2.should.equal 2
71
+ end
72
+
73
+ it 'maintains identity across different database handles of the same database' do
74
+ # If you try to subclass a class a second time with a different parent class,
75
+ # Ruby raises an exception.
76
+ should.not.raise do
77
+ original_handle = DBI::DatabaseHandle.last_handle
78
+
79
+ class Author < DBI::Model( :authors ); end
80
+
81
+ dbh = DBI.connect( "DBI:Pg:m4dbi", "m4dbi", "m4dbi" )
82
+ new_handle = DBI::DatabaseHandle.last_handle
83
+ new_handle.should.equal dbh
84
+ new_handle.should.not.equal original_handle
85
+
86
+ class Author < DBI::Model( :authors ); end
87
+ end
88
+ end
89
+
90
+ it 'maintains distinction from models of the same name in different databases' do
91
+ begin
92
+ a1 = @m_author[ 1 ]
93
+ a1.should.not.be.nil
94
+ a1.name.should.equal 'author1'
95
+
96
+ dbh = DBI.connect( "DBI:Pg:m4dbi2", "m4dbi", "m4dbi" )
97
+ reset_data( dbh, "test-data2.sql" )
98
+
99
+ @m_author2 = Class.new( DBI::Model( :authors ) )
100
+
101
+ @m_author2[ 1 ].should.be.nil
102
+ a11 = @m_author2[ 11 ]
103
+ a11.should.not.be.nil
104
+ a11.name.should.equal 'author11'
105
+
106
+ a2 = @m_author[ 2 ]
107
+ a2.should.not.be.nil
108
+ a2.name.should.equal 'author2'
109
+ ensure
110
+ # Clean up handles for later specs
111
+ dbh.disconnect if dbh and dbh.connected?
112
+ DBI.connect( "DBI:Pg:m4dbi", "m4dbi", "m4dbi" )
113
+ end
114
+ end
115
+
116
+ it 'raises an exception when creating with invalid arguments' do
117
+ should.raise( DBI::Error ) do
118
+ @m_author.new nil
119
+ end
120
+ should.raise( DBI::Error ) do
121
+ @m_author.new 2
122
+ end
123
+ should.raise( DBI::Error ) do
124
+ @m_author.new Object.new
125
+ end
126
+ end
127
+
128
+ it 'provides hash-like single-record access via #[ primary_key_value ]' do
129
+ o = @m_author[ 1 ]
130
+ o.should.not.be.nil
131
+ o.class.should.equal @m_author
132
+ o.name.should.equal 'author1'
133
+
134
+ o = @m_author[ 2 ]
135
+ o.should.not.be.nil
136
+ o.class.should.equal @m_author
137
+ o.name.should.equal 'author2'
138
+ end
139
+
140
+ it 'provides hash-like single-record access via #[ field_hash ]' do
141
+ o = @m_author[ :name => 'author1' ]
142
+ o.should.not.be.nil
143
+ o.class.should.equal @m_author
144
+ o.id.should.equal 1
145
+
146
+ o = @m_post[ :author_id => 1 ]
147
+ o.should.not.be.nil
148
+ o.class.should.equal @m_post
149
+ o.text.should.equal 'First post.'
150
+ end
151
+
152
+ it 'returns nil from #[] when no record is found' do
153
+ o = @m_author[ 999 ]
154
+ o.should.be.nil
155
+
156
+ o = @m_author[ :name => 'foobar' ]
157
+ o.should.be.nil
158
+ end
159
+
160
+ it 'provides multi-record access via #where( Hash )' do
161
+ posts = @m_post.where(
162
+ :author_id => 1
163
+ )
164
+ posts.should.not.be.nil
165
+ posts.should.not.be.empty
166
+ posts.size.should.equal 2
167
+ posts[ 0 ].class.should.equal @m_post
168
+
169
+ sorted_posts = posts.sort { |p1,p2|
170
+ p1._id <=> p2._id
171
+ }
172
+ p = sorted_posts.first
173
+ p.text.should.equal 'First post.'
174
+ end
175
+
176
+ it 'provides multi-record access via #where( String )' do
177
+ posts = @m_post.where( "id < 3" )
178
+ posts.should.not.be.nil
179
+ posts.should.not.be.empty
180
+ posts.size.should.equal 2
181
+ posts[ 0 ].class.should.equal @m_post
182
+
183
+ sorted_posts = posts.sort { |p1,p2|
184
+ p2._id <=> p1._id
185
+ }
186
+ p = sorted_posts.first
187
+ p.text.should.equal 'Second post.'
188
+ end
189
+
190
+ it 'returns an empty array from #where when no records are found' do
191
+ a = @m_author.where( :id => 999 )
192
+ a.should.be.empty
193
+
194
+ p = @m_post.where( "text = 'aoeu'" )
195
+ p.should.be.empty
196
+ end
197
+
198
+ it 'provides single-record access via #one_where( Hash )' do
199
+ post = @m_post.one_where( :author_id => 2 )
200
+ post.should.not.be.nil
201
+ post.class.should.equal @m_post
202
+ post.text.should.equal 'Second post.'
203
+ end
204
+
205
+ it 'provides single-record access via #one_where( String )' do
206
+ post = @m_post.one_where( "text LIKE '%Third%'" )
207
+ post.should.not.be.nil
208
+ post.class.should.equal @m_post
209
+ post.id.should.equal 3
210
+ end
211
+
212
+ it 'returns nil from #one_where when no record is found' do
213
+ a = @m_author.one_where( :id => 999 )
214
+ a.should.be.nil
215
+
216
+ p = @m_post.one_where( "text = 'aoeu'" )
217
+ p.should.be.nil
218
+ end
219
+
220
+
221
+ it 'returns all table records via #all' do
222
+ rows = @m_author.all
223
+ rows.should.not.be.nil
224
+ rows.should.not.be.empty
225
+ rows.size.should.equal 3
226
+
227
+ rows[ 0 ].id.should.equal 1
228
+ rows[ 0 ].name.should.equal 'author1'
229
+ rows[ 1 ].id.should.equal 2
230
+ rows[ 1 ].name.should.equal 'author2'
231
+ end
232
+
233
+ it 'returns an empty array when #all is called on an empty table' do
234
+ rows = @m_empty.all
235
+ rows.should.not.be.nil
236
+ rows.should.be.empty
237
+ end
238
+
239
+ it 'returns any single record from #one' do
240
+ one = @m_author.one
241
+ one.should.not.be.nil
242
+ one.class.should.equal @m_author
243
+ end
244
+
245
+ it 'returns nil from #one on an empty table' do
246
+ one = @m_empty.one
247
+ one.should.be.nil
248
+ end
249
+
250
+ it 'returns the record count via #count' do
251
+ n = @m_author.count
252
+ n.should.equal 3
253
+ end
254
+
255
+ it 'provides a means to create new records via #create( Hash )' do
256
+ a = @m_author.create(
257
+ :id => 9,
258
+ :name => 'author9'
259
+ )
260
+ a.should.not.be.nil
261
+ a.class.should.equal @m_author
262
+ a.id.should.equal 9
263
+ a.should.respond_to :name
264
+ a.should.not.respond_to :no_column_by_this_name
265
+ a.name.should.equal 'author9'
266
+
267
+ a_ = @m_author[ 9 ]
268
+ a_.should.not.be.nil
269
+ a_.should.equal a
270
+ a_.name.should.equal 'author9'
271
+
272
+ reset_data
273
+ end
274
+
275
+ it 'provides a means to create new records via #create { |r| }' do
276
+ should.raise( NoMethodError ) do
277
+ @m_author.create { |rec|
278
+ rec.no_such_column = 'foobar'
279
+ }
280
+ end
281
+
282
+ a = @m_author.create { |rec|
283
+ rec.id = 9
284
+ rec.name = 'author9'
285
+ }
286
+ a.should.not.be.nil
287
+ a.class.should.equal @m_author
288
+ a.id.should.equal 9
289
+ a.name.should.equal 'author9'
290
+
291
+ a_ = @m_author[ 9 ]
292
+ a_.should.equal a
293
+ a_.name.should.equal 'author9'
294
+
295
+ m = nil
296
+ should.not.raise do
297
+ m = @m_mc.create { |rec|
298
+ rec.id = 1
299
+ rec.c2 = 7
300
+ rec.c3 = 8
301
+ }
302
+ end
303
+ m_ = @m_mc[ 1 ]
304
+ m_.id.should.equal 1
305
+ m_.c1.should.be.nil
306
+ m_.c2.should.equal 7
307
+ m_.c3.should.equal 8
308
+ m_.c4.should.be.nil
309
+ m_.c5.should.be.nil
310
+
311
+ reset_data
312
+ end
313
+
314
+ it 'returns a record via #find_or_create( Hash )' do
315
+ n = @m_author.count
316
+ a = @m_author.find_or_create(
317
+ :id => 1,
318
+ :name => 'author1'
319
+ )
320
+ a.should.not.be.nil
321
+ a.class.should.equal @m_author
322
+ a.id.should.equal 1
323
+ a.should.respond_to :name
324
+ a.should.not.respond_to :no_column_by_this_name
325
+ a.name.should.equal 'author1'
326
+ @m_author.count.should.equal n
327
+ end
328
+
329
+ it 'creates a record via #find_or_create( Hash )' do
330
+ n = @m_author.count
331
+ a = @m_author.find_or_create(
332
+ :id => 9,
333
+ :name => 'author9'
334
+ )
335
+ a.should.not.be.nil
336
+ a.class.should.equal @m_author
337
+ a.id.should.equal 9
338
+ a.should.respond_to :name
339
+ a.should.not.respond_to :no_column_by_this_name
340
+ a.name.should.equal 'author9'
341
+ @m_author.count.should.equal n+1
342
+
343
+ a_ = @m_author[ 9 ]
344
+ a_.should.not.be.nil
345
+ a_.should.equal a
346
+ a_.name.should.equal 'author9'
347
+
348
+ reset_data
349
+ end
350
+
351
+ it 'provides a means to use generic raw SQL to select model instances' do
352
+ posts = @m_post.s(
353
+ %{
354
+ SELECT
355
+ p.*
356
+ FROM
357
+ posts p,
358
+ authors a
359
+ WHERE
360
+ p.author_id = a.id
361
+ AND a.name = ?
362
+ },
363
+ 'author1'
364
+ )
365
+ posts.should.not.be.nil
366
+ posts.should.not.be.empty
367
+ posts.size.should.equal 2
368
+
369
+ posts[ 0 ].id.should.equal 1
370
+ posts[ 0 ].text.should.equal 'First post.'
371
+ posts[ 0 ].class.should.equal @m_post
372
+ posts[ 1 ].id.should.equal 3
373
+ posts[ 1 ].text.should.equal 'Third post.'
374
+ posts[ 1 ].class.should.equal @m_post
375
+
376
+ no_posts = @m_post.s( "SELECT * FROM posts WHERE FALSE" )
377
+ no_posts.should.not.be.nil
378
+ no_posts.should.be.empty
379
+ end
380
+
381
+ it 'provides a means to use generic raw SQL to select one model instance' do
382
+ post = @m_post.s1(
383
+ %{
384
+ SELECT
385
+ p.*
386
+ FROM
387
+ posts p,
388
+ authors a
389
+ WHERE
390
+ p.author_id = a.id
391
+ AND a.name = ?
392
+ ORDER BY
393
+ id DESC
394
+ },
395
+ 'author1'
396
+ )
397
+
398
+ post.should.not.be.nil
399
+ post.class.should.equal @m_post
400
+
401
+ post.id.should.equal 3
402
+ post.author_id.should.equal 1
403
+ post.text.should.equal 'Third post.'
404
+
405
+ no_post = @m_post.s1( "SELECT * FROM posts WHERE FALSE" )
406
+ no_post.should.be.nil
407
+ end
408
+
409
+ it 'is Enumerable' do
410
+ should.not.raise do
411
+ @m_author.each { |a| }
412
+ names = @m_author.map { |a| a.name }
413
+ names.find { |name| name == 'author1' }.should.not.be.nil
414
+ names.find { |name| name == 'author2' }.should.not.be.nil
415
+ names.find { |name| name == 'author3' }.should.not.be.nil
416
+ names.find { |name| name == 'author99' }.should.be.nil
417
+ end
418
+ authors = []
419
+ @m_author.each do |a|
420
+ authors << a
421
+ end
422
+ authors.find { |a| a.name == 'author1' }.should.not.be.nil
423
+ authors.find { |a| a.name == 'author2' }.should.not.be.nil
424
+ authors.find { |a| a.name == 'author3' }.should.not.be.nil
425
+ authors.find { |a| a.name == 'author99' }.should.be.nil
426
+ end
427
+
428
+ it 'provides a means to update records referred to by primary key value' do
429
+ new_text = 'This is some new text.'
430
+
431
+ p2 = @m_post[ 2 ]
432
+ p2.text.should.not.equal new_text
433
+
434
+ @m_post.update_one( 2, { :text => new_text } )
435
+
436
+ p2_ = @m_post[ 2 ]
437
+ p2_.text.should.equal new_text
438
+
439
+ reset_data
440
+ end
441
+
442
+ it 'provides a means to update records referred to by a value hash' do
443
+ new_text = 'This is some new text.'
444
+
445
+ posts = @m_post.where( :author_id => 1 )
446
+ posts.size.should.equal 2
447
+ posts.find_all { |p| p.text == new_text }.should.be.empty
448
+
449
+ @m_post.update(
450
+ { :author_id => 1 },
451
+ { :text => new_text }
452
+ )
453
+
454
+ posts_ = @m_post.where( :author_id => 1 )
455
+ posts_.size.should.equal 2
456
+ posts_.find_all { |p| p.text == new_text }.should.equal posts_
457
+
458
+ reset_data
459
+ end
460
+
461
+ it 'provides a means to update records specified by a raw WHERE clause' do
462
+ new_text = 'This is some new text.'
463
+
464
+ posts = @m_post.where( :author_id => 1 )
465
+ posts.size.should.equal 2
466
+ posts.find_all { |p| p.text == new_text }.should.be.empty
467
+
468
+ @m_post.update(
469
+ "author_id < 2",
470
+ { :text => new_text }
471
+ )
472
+
473
+ posts_ = @m_post.where( :author_id => 1 )
474
+ posts_.size.should.equal 2
475
+ posts_.find_all { |p| p.text == new_text }.should.equal posts_
476
+
477
+ reset_data
478
+ end
479
+ end
480
+
481
+ describe 'A created DBI::Model subclass instance' do
482
+ before do
483
+ @m_mc = Class.new( DBI::Model( :many_col_table ) )
484
+ @m_author = Class.new( DBI::Model( :authors ) )
485
+ @m_post = Class.new( DBI::Model( :posts ) )
486
+ end
487
+
488
+ it 'provides read access to fields via identically-named readers' do
489
+ mc = @m_mc.create(
490
+ :c3 => 3,
491
+ :c4 => 4
492
+ )
493
+ mc.should.not.be.nil
494
+ should.not.raise do
495
+ mc.id
496
+ mc.c1
497
+ mc.c2
498
+ mc.c5
499
+ end
500
+ mc.id.should.not.be.nil
501
+ mc.c3.should.equal 3
502
+ mc.c4.should.equal 4
503
+ end
504
+
505
+ it 'provides write access to fields via identically-named writers' do
506
+ mc = @m_mc.create(
507
+ :c3 => 30,
508
+ :c4 => 40
509
+ )
510
+ mc.should.not.be.nil
511
+ mc.c1 = 10
512
+ mc.c2 = 20
513
+ mc.c1.should.equal 10
514
+ mc.c2.should.equal 20
515
+ mc.c3.should.equal 30
516
+ mc.c4.should.equal 40
517
+ id_ = mc.id
518
+ id_.should.not.be.nil
519
+
520
+ mc_ = @m_mc[ id_ ]
521
+ mc_.id.should.equal id_
522
+ mc_.c1.should.equal 10
523
+ mc_.c2.should.equal 20
524
+ mc_.c3.should.equal 30
525
+ mc_.c4.should.equal 40
526
+ end
527
+
528
+ it 'maintains Hash key equality across different fetches' do
529
+ h = Hash.new
530
+ a = @m_author[ 1 ]
531
+ h[ a ] = 123
532
+ a_ = @m_author[ 1 ]
533
+ h[ a_].should.equal 123
534
+
535
+ a2 = @m_author[ 2 ]
536
+ h[ a2 ].should.be.nil
537
+
538
+ h[ a2 ] = 456
539
+ h[ a ].should.equal 123
540
+ h[ a_ ].should.equal 123
541
+
542
+ a2_ = @m_author[ 2 ]
543
+ h[ a2_ ].should.equal 456
544
+ end
545
+
546
+ it 'maintains Hash key distinction for different Model subclasses' do
547
+ h = Hash.new
548
+ a = @m_author[ 1 ]
549
+ h[ a ] = 123
550
+ p = @m_post[ 1 ]
551
+ h[ p ] = 456
552
+ h[ p ].should.equal 456
553
+
554
+ a_ = @m_author[ 1 ]
555
+ h[ a_ ].should.equal 123
556
+ end
557
+ end
558
+
559
+ describe 'A found DBI::Model subclass instance' do
560
+ before do
561
+ @m_author = Class.new( DBI::Model( :authors ) )
562
+ @m_post = Class.new( DBI::Model( :posts ) )
563
+ end
564
+
565
+ it 'provides access to primary key value' do
566
+ a = @m_author[ 1 ]
567
+ a.pk.should.equal 1
568
+
569
+ p = @m_post[ 3 ]
570
+ p.pk.should.equal 3
571
+ end
572
+
573
+ it 'provides read access to fields via identically-named readers' do
574
+ p = @m_post[ 2 ]
575
+
576
+ should.not.raise( NoMethodError ) do
577
+ p.id
578
+ p.author_id
579
+ p.text
580
+ end
581
+
582
+ should.raise( NoMethodError ) do
583
+ p.foobar
584
+ end
585
+
586
+ p.id.should.equal 2
587
+ p.author_id.should.equal 2
588
+ p.text.should.equal 'Second post.'
589
+ end
590
+
591
+ it 'provides write access to fields via identically-named writers' do
592
+ the_new_text = 'Here is some new text.'
593
+
594
+ p2 = @m_post[ 2 ]
595
+
596
+ p3 = @m_post[ 3 ]
597
+ p3.text = the_new_text
598
+
599
+ p3_ = @m_post[ 3 ]
600
+ p3_.text.should.equal the_new_text
601
+
602
+ # Shouldn't change other rows
603
+ p2_ = @m_post[ 2 ]
604
+ p2_.text.should.equal p2.text
605
+
606
+ reset_data
607
+ end
608
+
609
+ it 'maintains identity across multiple DB hits' do
610
+ px = @m_post[ 1 ]
611
+ py = @m_post[ 1 ]
612
+
613
+ px.should.equal py
614
+ end
615
+
616
+ it 'provides multi-column writability via Model#set' do
617
+ p = @m_post[ 1 ]
618
+ the_new_text = 'The 3rd post.'
619
+ p.set(
620
+ :author_id => 2,
621
+ :text => the_new_text
622
+ )
623
+
624
+ p_ = @m_post[ 1 ]
625
+ p_.author_id.should.equal 2
626
+ p_.text.should.equal the_new_text
627
+
628
+ reset_data
629
+ end
630
+
631
+ it 'is deleted by #delete' do
632
+ p = @m_post[ 3 ]
633
+ p.should.not.be.nil
634
+ successfully_deleted = p.delete
635
+ successfully_deleted.should.be.true
636
+ @m_post[ 3 ].should.be.nil
637
+
638
+ reset_data
639
+ end
640
+
641
+ it 'does nothing on #save' do
642
+ p = @m_post[ 1 ]
643
+ should.not.raise do
644
+ p.save
645
+ end
646
+ end
647
+
648
+ it 'allows a field to be incremented' do
649
+ mc = ManyCol.create( :c1 => 50 )
650
+ should.not.raise do
651
+ mc.inc
652
+ end
653
+ end
654
+ it 'allows a field to be decremented' do
655
+ mc = ManyCol.create( :c1 => 50 )
656
+ should.not.raise do
657
+ mc.dec
658
+ end
659
+ end
660
+ end
661
+
662
+ describe 'DBI::Model (relationships)' do
663
+ before do
664
+ @m_author = Class.new( DBI::Model( :authors ) )
665
+ @m_post = Class.new( DBI::Model( :posts ) )
666
+ @m_fan = Class.new( DBI::Model( :fans ) )
667
+ end
668
+
669
+ it 'facilitates relating one to many, providing read access' do
670
+ DBI::Model.one_to_many( @m_author, @m_post, :posts, :author, :author_id )
671
+ a = @m_author[ 1 ]
672
+ a.posts.should.not.be.empty
673
+ p = @m_post[ 3 ]
674
+ p.author.should.not.be.nil
675
+ p.author.id.should.equal 1
676
+ end
677
+
678
+ it 'facilitates relating one to many, allowing one of the many to set its one' do
679
+ DBI::Model.one_to_many(
680
+ @m_author, @m_post, :posts, :author, :author_id
681
+ )
682
+ p = @m_post[ 3 ]
683
+ p.author.should.not.be.nil
684
+ p.author.id.should.equal 1
685
+ p.author = @m_author.create( :id => 4, :name => 'author4' )
686
+ p_ = @m_post[ 3 ]
687
+ p_.author.id.should.equal 4
688
+
689
+ reset_data
690
+ end
691
+
692
+ it 'facilitates relating many to many, providing read access' do
693
+ DBI::Model.many_to_many(
694
+ @m_author, @m_fan, :authors_liked, :fans, :authors_fans, :author_id, :fan_id
695
+ )
696
+ a1 = @m_author[ 1 ]
697
+ a2 = @m_author[ 2 ]
698
+ f2 = @m_fan[ 2 ]
699
+ f3 = @m_fan[ 3 ]
700
+
701
+ a1f = a1.fans
702
+ a1f.should.not.be.nil
703
+ a1f.should.not.be.empty
704
+ a1f.size.should.equal 2
705
+ a1f[ 0 ].class.should.equal @m_fan
706
+ a1f.find { |f| f.name == 'fan1' }.should.be.nil
707
+ a1f.find { |f| f.name == 'fan2' }.should.not.be.nil
708
+ a1f.find { |f| f.name == 'fan3' }.should.not.be.nil
709
+
710
+ a2f = a2.fans
711
+ a2f.should.not.be.nil
712
+ a2f.should.not.be.empty
713
+ a2f.size.should.equal 2
714
+ a2f[ 0 ].class.should.equal @m_fan
715
+ a2f.find { |f| f.name == 'fan1' }.should.be.nil
716
+ a2f.find { |f| f.name == 'fan3' }.should.not.be.nil
717
+ a2f.find { |f| f.name == 'fan4' }.should.not.be.nil
718
+
719
+ f2a = f2.authors_liked
720
+ f2a.should.not.be.nil
721
+ f2a.should.not.be.empty
722
+ f2a.size.should.equal 1
723
+ f2a[ 0 ].class.should.equal @m_author
724
+ f2a[ 0 ].name.should.equal 'author1'
725
+
726
+ f3a = f3.authors_liked
727
+ f3a.should.not.be.nil
728
+ f3a.should.not.be.empty
729
+ f3a.size.should.equal 2
730
+ f3a.find { |a| a.name == 'author1' }.should.not.be.nil
731
+ f3a.find { |a| a.name == 'author2' }.should.not.be.nil
732
+ f3a.find { |a| a.name == 'author3' }.should.be.nil
733
+
734
+ @m_author[ 3 ].fans.should.be.empty
735
+ @m_fan[ 5 ].authors_liked.should.be.empty
736
+ end
737
+ end
738
+
739
+ describe 'DBI::Collection' do
740
+ before do
741
+ @m_author = Class.new( DBI::Model( :authors ) )
742
+ @m_post = Class.new( DBI::Model( :posts ) )
743
+ @m_fan = Class.new( DBI::Model( :fans ) )
744
+
745
+ DBI::Model.one_to_many(
746
+ @m_author, @m_post, :posts, :author, :author_id
747
+ )
748
+ end
749
+
750
+ it 'accepts additions' do
751
+ a = @m_author[ 1 ]
752
+ the_text = 'A new post.'
753
+ a.posts << { :text => the_text }
754
+ p = a.posts.find { |p| p.text == the_text }
755
+ p.should.not.be.nil
756
+ p.author.should.equal a
757
+
758
+ a_ = @m_author[ 1 ]
759
+ a_.posts.find { |p| p.text == the_text }.should.not.be.nil
760
+
761
+ reset_data
762
+ end
763
+
764
+ it 'facilitates single record deletions' do
765
+ a = @m_author[ 1 ]
766
+ posts = a.posts
767
+ n = posts.size
768
+ p = posts[ 0 ]
769
+
770
+ posts.delete( p ).should.be.true
771
+ a.posts.size.should.equal( n - 1 )
772
+ posts.find { |p_| p_ == p }.should.be.nil
773
+
774
+ reset_data
775
+ end
776
+
777
+ it 'facilitates multi-record deletions' do
778
+ a = @m_author[ 1 ]
779
+ posts = a.posts
780
+ n = posts.size
781
+ posts.delete( :text => 'Third post.' ).should.equal 1
782
+ a.posts.size.should.equal( n - 1 )
783
+ posts.find { |p| p.text == 'Third post.' }.should.be.nil
784
+ posts.find { |p| p.text == 'First post.' }.should.not.be.nil
785
+
786
+ reset_data
787
+ end
788
+
789
+ it 'facilitates table-wide deletion' do
790
+ a = @m_author[ 1 ]
791
+ a.posts.should.not.be.empty
792
+ a.posts.clear.should.be > 0
793
+ a.posts.should.be.empty
794
+
795
+ reset_data
796
+ end
797
+ end