m4dbi 0.5.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/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