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/CHANGELOG +3 -0
- data/HIM +50 -0
- data/READHIM +1 -0
- data/lib/m4dbi/collection.rb +69 -0
- data/lib/m4dbi/database-handle.rb +86 -0
- data/lib/m4dbi/hash.rb +21 -0
- data/lib/m4dbi/model.rb +372 -0
- data/lib/m4dbi/row.rb +35 -0
- data/lib/m4dbi/traits.rb +91 -0
- data/lib/m4dbi.rb +8 -0
- data/spec/dbi.rb +128 -0
- data/spec/hash.rb +27 -0
- data/spec/helper.rb +13 -0
- data/spec/model.rb +797 -0
- metadata +80 -0
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
|