m4dbi 0.6.1 → 0.6.2
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +5 -0
- data/lib/m4dbi/collection.rb +6 -6
- data/lib/m4dbi/model.rb +38 -18
- data/lib/m4dbi/row.rb +2 -2
- data/lib/m4dbi.rb +1 -1
- data/spec/dbi.rb +32 -28
- data/spec/model.rb +49 -5
- metadata +14 -12
data/CHANGELOG
CHANGED
data/lib/m4dbi/collection.rb
CHANGED
@@ -5,23 +5,23 @@ module DBI
|
|
5
5
|
@the_many_model = the_many_model
|
6
6
|
@the_one_fk = the_one_fk
|
7
7
|
end
|
8
|
-
|
8
|
+
|
9
9
|
def elements
|
10
10
|
@the_many_model.where( @the_one_fk => @the_one.pk )
|
11
11
|
end
|
12
12
|
alias copy elements
|
13
|
-
|
13
|
+
|
14
14
|
def method_missing( method, *args, &blk )
|
15
15
|
elements.send( method, *args, &blk )
|
16
16
|
end
|
17
|
-
|
17
|
+
|
18
18
|
def push( new_item_hash )
|
19
19
|
new_item_hash[ @the_one_fk ] = @the_one.pk
|
20
20
|
@the_many_model.create( new_item_hash )
|
21
21
|
end
|
22
22
|
alias << push
|
23
23
|
alias add push
|
24
|
-
|
24
|
+
|
25
25
|
def delete( arg )
|
26
26
|
case arg
|
27
27
|
when @the_many_model
|
@@ -30,7 +30,7 @@ module DBI
|
|
30
30
|
DELETE FROM #{@the_many_model.table}
|
31
31
|
WHERE
|
32
32
|
#{@the_one_fk} = ?
|
33
|
-
AND #{@the_many_model.
|
33
|
+
AND #{@the_many_model.pk_clause}
|
34
34
|
},
|
35
35
|
@the_one.pk,
|
36
36
|
arg.pk
|
@@ -54,7 +54,7 @@ module DBI
|
|
54
54
|
)
|
55
55
|
end
|
56
56
|
end
|
57
|
-
|
57
|
+
|
58
58
|
# Returns the number of records deleted
|
59
59
|
def clear
|
60
60
|
@the_many_model.dbh.do(
|
data/lib/m4dbi/model.rb
CHANGED
@@ -103,15 +103,13 @@ module DBI
|
|
103
103
|
|
104
104
|
def self.create( hash = {} )
|
105
105
|
if block_given?
|
106
|
-
|
107
|
-
|
108
|
-
[ M4DBI_UNASSIGNED ] * columns.size
|
109
|
-
)
|
106
|
+
struct = Struct.new( *( columns.collect { |c| c[ 'name' ].to_sym } ) )
|
107
|
+
row = struct.new( *( [ M4DBI_UNASSIGNED ] * columns.size ) )
|
110
108
|
yield row
|
111
|
-
hash =
|
112
|
-
|
113
|
-
if
|
114
|
-
hash
|
109
|
+
hash = {}
|
110
|
+
row.members.each do |k|
|
111
|
+
if row[ k ] != M4DBI_UNASSIGNED
|
112
|
+
hash[ k ] = row[ k ]
|
115
113
|
end
|
116
114
|
end
|
117
115
|
end
|
@@ -154,13 +152,13 @@ module DBI
|
|
154
152
|
|
155
153
|
def self.find_or_create( hash = nil )
|
156
154
|
item = nil
|
157
|
-
error =
|
155
|
+
error = DBI::Error.new( "Failed to find_or_create( #{hash.inspect} )" )
|
158
156
|
item = self.one_where( hash )
|
159
157
|
if item.nil?
|
160
158
|
item =
|
161
159
|
begin
|
162
160
|
self.create( hash )
|
163
|
-
rescue => error
|
161
|
+
rescue Exception => error
|
164
162
|
self.one_where( hash )
|
165
163
|
end
|
166
164
|
end
|
@@ -293,8 +291,21 @@ module DBI
|
|
293
291
|
begin
|
294
292
|
@row.send( method, *args )
|
295
293
|
rescue NoMethodError => e
|
294
|
+
if e.backtrace.grep /method_missing/
|
295
|
+
# Prevent infinite recursion
|
296
|
+
self_str = 'model object'
|
297
|
+
elsif self.respond_to? :to_s
|
298
|
+
self_str = self.to_s
|
299
|
+
elsif self.respond_to? :inspect
|
300
|
+
self_str = self.inspect
|
301
|
+
elsif self.respond_to? :class
|
302
|
+
self_str = "#{self.class} object"
|
303
|
+
else
|
304
|
+
self_str = "instance of unknown model"
|
305
|
+
end
|
306
|
+
|
296
307
|
raise NoMethodError.new(
|
297
|
-
"undefined method '#{method}' for #{
|
308
|
+
"undefined method '#{method}' for #{self_str}",
|
298
309
|
method,
|
299
310
|
args
|
300
311
|
)
|
@@ -372,13 +383,14 @@ module DBI
|
|
372
383
|
|
373
384
|
# Define a new DBI::Model like this:
|
374
385
|
# class Post < DBI::Model( :posts ); end
|
375
|
-
# You can specify the primary key column(s) using an
|
376
|
-
# class Author < DBI::Model( :authors, [ 'auth_num' ] ); end
|
377
|
-
def self.Model( table,
|
378
|
-
h = DBI::DatabaseHandle.last_handle
|
386
|
+
# You can specify the primary key column(s) using an option, like so:
|
387
|
+
# class Author < DBI::Model( :authors, pk: [ 'auth_num' ] ); end
|
388
|
+
def self.Model( table, options = Hash.new )
|
389
|
+
h = options[ :dbh ] || DBI::DatabaseHandle.last_handle
|
379
390
|
if h.nil? or not h.connected?
|
380
391
|
raise DBI::Error.new( "Attempted to create a Model class without first connecting to a database." )
|
381
392
|
end
|
393
|
+
pk_ = options[ :pk ] || [ 'id' ]
|
382
394
|
if not pk_.respond_to? :each
|
383
395
|
raise DBI::Error.new( "Primary key must be enumerable (was given #{pk_.inspect})" )
|
384
396
|
end
|
@@ -400,18 +412,26 @@ module DBI
|
|
400
412
|
:columns => h.columns( table.to_s ),
|
401
413
|
} )
|
402
414
|
|
415
|
+
meta_def( 'pk_str'.to_sym ) do
|
416
|
+
if pk.size == 1
|
417
|
+
pk[ 0 ].to_s
|
418
|
+
else
|
419
|
+
pk.to_s
|
420
|
+
end
|
421
|
+
end
|
422
|
+
|
403
423
|
if defined?( DBI::DBD::Pg::Database ) and DBI::DBD::Pg::Database === h.handle
|
404
424
|
# TODO: This is broken for non-SERIAL or multi-column primary keys
|
405
425
|
meta_def( "last_record".to_sym ) do |dbh_|
|
406
|
-
self.s1 "SELECT * FROM #{table} WHERE #{
|
426
|
+
self.s1 "SELECT * FROM #{table} WHERE #{pk_str} = currval( '#{table}_#{pk_str}_seq' );"
|
407
427
|
end
|
408
428
|
elsif defined?( DBI::DBD::Mysql::Database ) and DBI::DBD::Mysql::Database === h.handle
|
409
429
|
meta_def( "last_record".to_sym ) do |dbh_|
|
410
|
-
self.s1 "SELECT * FROM #{table} WHERE #{
|
430
|
+
self.s1 "SELECT * FROM #{table} WHERE #{pk_str} = LAST_INSERT_ID();"
|
411
431
|
end
|
412
432
|
elsif defined?( DBI::DBD::SQLite3::Database ) and DBI::DBD::SQLite3::Database === h.handle
|
413
433
|
meta_def( "last_record".to_sym ) do |dbh_|
|
414
|
-
self.s1 "SELECT * FROM #{table} WHERE #{
|
434
|
+
self.s1 "SELECT * FROM #{table} WHERE #{pk_str} = last_insert_rowid();"
|
415
435
|
end
|
416
436
|
# TODO: more DBDs
|
417
437
|
end
|
data/lib/m4dbi/row.rb
CHANGED
@@ -9,7 +9,7 @@ module DBI
|
|
9
9
|
field = convert_alternate_fieldname( field )
|
10
10
|
end
|
11
11
|
if @column_names.include?( field )
|
12
|
-
self[ field ] =
|
12
|
+
self[ field ] = args[ 0 ]
|
13
13
|
else
|
14
14
|
super
|
15
15
|
end
|
@@ -27,7 +27,7 @@ module DBI
|
|
27
27
|
end
|
28
28
|
end
|
29
29
|
end
|
30
|
-
|
30
|
+
|
31
31
|
def convert_alternate_fieldname( field )
|
32
32
|
field.gsub( /(^_)|(_$)/ , '' )
|
33
33
|
end
|
data/lib/m4dbi.rb
CHANGED
data/spec/dbi.rb
CHANGED
@@ -4,31 +4,31 @@ $dbh = connect_to_spec_database
|
|
4
4
|
reset_data
|
5
5
|
|
6
6
|
describe 'DBI::DatabaseHandle#select_column' do
|
7
|
-
|
7
|
+
|
8
8
|
it 'selects one column' do
|
9
9
|
name = $dbh.select_column(
|
10
10
|
"SELECT name FROM authors LIMIT 1"
|
11
11
|
)
|
12
12
|
name.class.should.not.equal Array
|
13
13
|
name.should.equal 'author1'
|
14
|
-
|
14
|
+
|
15
15
|
null = $dbh.select_column(
|
16
16
|
"SELECT c4 FROM many_col_table WHERE c3 = 40"
|
17
17
|
)
|
18
18
|
null.should.be.nil
|
19
|
-
|
19
|
+
|
20
20
|
should.raise( DBI::DataError ) do
|
21
21
|
$dbh.select_column( "SELECT name FROM authors WHERE 1+1 = 3" )
|
22
22
|
end
|
23
23
|
end
|
24
|
-
|
24
|
+
|
25
25
|
it 'selects one column of first row' do
|
26
26
|
name = $dbh.select_column(
|
27
27
|
"SELECT name FROM authors ORDER BY name DESC"
|
28
28
|
)
|
29
29
|
name.should.equal 'author3'
|
30
30
|
end
|
31
|
-
|
31
|
+
|
32
32
|
it 'selects first column of first row' do
|
33
33
|
name = $dbh.select_column(
|
34
34
|
"SELECT name, id FROM authors ORDER BY name DESC"
|
@@ -38,50 +38,54 @@ describe 'DBI::DatabaseHandle#select_column' do
|
|
38
38
|
end
|
39
39
|
|
40
40
|
describe 'DBI::DatabaseHandle#one_transaction' do
|
41
|
-
|
41
|
+
|
42
42
|
it 'turns off autocommit for the duration of a single transaction' do
|
43
43
|
$dbh.d( "DELETE FROM many_col_table;" )
|
44
44
|
$dbh.i( "INSERT INTO many_col_table ( id, c1 ) VALUES ( 1, 10 );" )
|
45
|
-
|
45
|
+
|
46
46
|
# Here we will attempt to increment a value two times in parallel.
|
47
47
|
# If each multi-operation transaction is truly atomic, we expect that
|
48
48
|
# the final value will reflect two increments.
|
49
49
|
# If atomicity is not respected, the value should only reflect one
|
50
50
|
# increment.
|
51
|
-
|
51
|
+
|
52
52
|
# First, we test the non-transactional case, to show failure.
|
53
|
-
|
53
|
+
|
54
54
|
thread1 = Thread.new do
|
55
55
|
value = $dbh.sc "SELECT c1 FROM many_col_table WHERE id = 1;"
|
56
56
|
value.should.equal 10
|
57
|
-
sleep
|
57
|
+
sleep 1 # seconds
|
58
58
|
$dbh.u "UPDATE many_col_table SET c1 = ?", ( value + 1 )
|
59
59
|
end
|
60
|
-
|
60
|
+
|
61
|
+
sleep 0.5
|
62
|
+
|
61
63
|
thread2 = Thread.new do
|
62
64
|
value = $dbh.sc "SELECT c1 FROM many_col_table WHERE id = 1;"
|
63
65
|
value.should.equal 10
|
64
66
|
# Update right away
|
65
67
|
$dbh.u "UPDATE many_col_table SET c1 = ?", ( value + 1 )
|
66
68
|
end
|
67
|
-
|
69
|
+
|
68
70
|
thread2.join
|
69
71
|
thread1.join
|
70
|
-
|
72
|
+
|
71
73
|
value = $dbh.sc "SELECT c1 FROM many_col_table WHERE id = 1;"
|
72
74
|
# Failure; two increments should give a final value of 12.
|
73
75
|
value.should.equal( 10 + 1 )
|
74
|
-
|
76
|
+
|
75
77
|
# Now, we show that transactions keep things sane.
|
76
|
-
|
78
|
+
|
77
79
|
thread1 = Thread.new do
|
78
80
|
$dbh.one_transaction do |dbh|
|
79
81
|
value = dbh.sc "SELECT c1 FROM many_col_table WHERE id = 1;"
|
80
|
-
sleep
|
82
|
+
sleep 1 # seconds
|
81
83
|
dbh.u "UPDATE many_col_table SET c1 = ?", ( value + 1 )
|
82
84
|
end
|
83
85
|
end
|
84
|
-
|
86
|
+
|
87
|
+
sleep 0.5
|
88
|
+
|
85
89
|
thread2 = Thread.new do
|
86
90
|
$dbh.one_transaction do |dbh|
|
87
91
|
value = dbh.sc "SELECT c1 FROM many_col_table WHERE id = 1;"
|
@@ -89,50 +93,50 @@ describe 'DBI::DatabaseHandle#one_transaction' do
|
|
89
93
|
dbh.u "UPDATE many_col_table SET c1 = ?", ( value + 1 )
|
90
94
|
end
|
91
95
|
end
|
92
|
-
|
96
|
+
|
93
97
|
thread2.join
|
94
98
|
thread1.join
|
95
|
-
|
99
|
+
|
96
100
|
value = $dbh.sc "SELECT c1 FROM many_col_table WHERE id = 1;"
|
97
101
|
value.should.equal( 11 + 1 + 1 )
|
98
|
-
|
102
|
+
|
99
103
|
reset_data
|
100
104
|
end
|
101
|
-
|
105
|
+
|
102
106
|
end
|
103
107
|
|
104
108
|
describe 'DBI::Row accessors' do
|
105
|
-
|
109
|
+
|
106
110
|
it 'provide read access via #fieldname' do
|
107
111
|
row = $dbh.select_one(
|
108
112
|
"SELECT * FROM posts ORDER BY author_id DESC LIMIT 1"
|
109
113
|
)
|
110
114
|
row.should.not.equal nil
|
111
|
-
|
115
|
+
|
112
116
|
row._id.should.be.same_as row[ 'id' ]
|
113
117
|
row.id_.should.be.same_as row[ 'id' ]
|
114
118
|
row.author_id.should.be.same_as row[ 'author_id' ]
|
115
119
|
row.text.should.be.same_as row[ 'text' ]
|
116
|
-
|
120
|
+
|
117
121
|
row.text.should.equal 'Second post.'
|
118
122
|
end
|
119
|
-
|
123
|
+
|
120
124
|
it 'provide in-memory (non-syncing) write access via #fieldname=' do
|
121
125
|
row = $dbh.select_one(
|
122
126
|
"SELECT * FROM posts ORDER BY author_id DESC LIMIT 1"
|
123
127
|
)
|
124
128
|
row.should.not.equal nil
|
125
|
-
|
129
|
+
|
126
130
|
old_id = row._id
|
127
131
|
row.id = old_id + 1
|
128
132
|
row._id.should.not.equal old_id
|
129
133
|
row._id.should.equal( old_id + 1 )
|
130
|
-
|
134
|
+
|
131
135
|
old_text = row.text
|
132
136
|
new_text = 'This is the new post text.'
|
133
137
|
row.text = new_text
|
134
138
|
row.text.should.not.equal old_text
|
135
139
|
row.text.should.equal new_text
|
136
140
|
end
|
137
|
-
|
141
|
+
|
138
142
|
end
|
data/spec/model.rb
CHANGED
@@ -34,8 +34,8 @@ describe 'A DBI::Model subclass' do
|
|
34
34
|
@m_post = Class.new( DBI::Model( :posts ) )
|
35
35
|
@m_empty = Class.new( DBI::Model( :empty_table ) )
|
36
36
|
@m_mc = Class.new( DBI::Model( :many_col_table ) )
|
37
|
-
@m_nipk = Class.new( DBI::Model( :non_id_pk, [ :str ] ) )
|
38
|
-
@m_mcpk = Class.new( DBI::Model( :mcpk, [ :kc1, :kc2 ] ) )
|
37
|
+
@m_nipk = Class.new( DBI::Model( :non_id_pk, :pk => [ :str ] ) )
|
38
|
+
@m_mcpk = Class.new( DBI::Model( :mcpk, :pk => [ :kc1, :kc2 ] ) )
|
39
39
|
class Author < DBI::Model( :authors ); end
|
40
40
|
end
|
41
41
|
|
@@ -90,6 +90,7 @@ describe 'A DBI::Model subclass' do
|
|
90
90
|
reset_data( dbh, "test-data2.sql" )
|
91
91
|
|
92
92
|
@m_author2 = Class.new( DBI::Model( :authors ) )
|
93
|
+
@m_author2.dbh.should.equal dbh
|
93
94
|
|
94
95
|
@m_author2[ 1 ].should.be.nil
|
95
96
|
a11 = @m_author2[ 11 ]
|
@@ -101,7 +102,34 @@ describe 'A DBI::Model subclass' do
|
|
101
102
|
a2.name.should.equal 'author2'
|
102
103
|
ensure
|
103
104
|
# Clean up handles for later specs
|
104
|
-
|
105
|
+
# puts dbh.object_id
|
106
|
+
# dbh.disconnect if dbh and dbh.connected?
|
107
|
+
connect_to_spec_database
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'can use a specific database handle' do
|
112
|
+
begin
|
113
|
+
dbh1 = connect_to_spec_database
|
114
|
+
dbh1.should.equal DBI::DatabaseHandle.last_handle
|
115
|
+
dbh2 = connect_to_spec_database( ENV[ 'M4DBI_DATABASE2' ] || 'm4dbi2' )
|
116
|
+
dbh2.should.equal DBI::DatabaseHandle.last_handle
|
117
|
+
reset_data( dbh2, "test-data2.sql" )
|
118
|
+
|
119
|
+
dbh1.should.not.equal dbh2
|
120
|
+
|
121
|
+
class Author1 < DBI::Model( :authors, :dbh => dbh1 ); end
|
122
|
+
class Author2 < DBI::Model( :authors, :dbh => dbh2 ); end
|
123
|
+
|
124
|
+
a1 = Author1[ 1 ]
|
125
|
+
a1.should.not.be.nil
|
126
|
+
a1.name.should.equal 'author1'
|
127
|
+
|
128
|
+
a11 = Author2[ 11 ]
|
129
|
+
a11.should.not.be.nil
|
130
|
+
a11.name.should.equal 'author11'
|
131
|
+
ensure
|
132
|
+
# Clean up handles for later specs
|
105
133
|
connect_to_spec_database
|
106
134
|
end
|
107
135
|
end
|
@@ -348,6 +376,22 @@ describe 'A DBI::Model subclass' do
|
|
348
376
|
a_.should.equal a
|
349
377
|
a_.name.should.equal 'author9'
|
350
378
|
|
379
|
+
# No value given for auto-incrementing primary key
|
380
|
+
a = @m_author.create(
|
381
|
+
:name => 'author10'
|
382
|
+
)
|
383
|
+
a.should.not.be.nil
|
384
|
+
a.class.should.equal @m_author
|
385
|
+
a.id.should.not.equal 9
|
386
|
+
a.should.respond_to :name
|
387
|
+
a.should.not.respond_to :no_column_by_this_name
|
388
|
+
a.name.should.equal 'author10'
|
389
|
+
|
390
|
+
a_ = @m_author[ a.id ]
|
391
|
+
a_.should.not.be.nil
|
392
|
+
a_.should.equal a
|
393
|
+
a_.name.should.equal 'author10'
|
394
|
+
|
351
395
|
reset_data
|
352
396
|
end
|
353
397
|
|
@@ -658,8 +702,8 @@ describe 'A found DBI::Model subclass instance' do
|
|
658
702
|
@m_author = Class.new( DBI::Model( :authors ) )
|
659
703
|
@m_post = Class.new( DBI::Model( :posts ) )
|
660
704
|
@m_mc = Class.new( DBI::Model( :many_col_table ) )
|
661
|
-
@m_nipk = Class.new( DBI::Model( :non_id_pk, [ :str ] ) )
|
662
|
-
@m_mcpk = Class.new( DBI::Model( :mcpk, [ :kc1, :kc2 ] ) )
|
705
|
+
@m_nipk = Class.new( DBI::Model( :non_id_pk, :pk => [ :str ] ) )
|
706
|
+
@m_mcpk = Class.new( DBI::Model( :mcpk, :pk => [ :kc1, :kc2 ] ) )
|
663
707
|
end
|
664
708
|
|
665
709
|
it 'provides access to primary key value' do
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: m4dbi
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.6.
|
4
|
+
version: 0.6.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Pistos
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-
|
12
|
+
date: 2009-07-10 00:00:00 -04:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -48,21 +48,23 @@ files:
|
|
48
48
|
- READHIM
|
49
49
|
- CHANGELOG
|
50
50
|
- LICENCE
|
51
|
-
- lib/m4dbi
|
52
|
-
- lib/m4dbi/collection.rb
|
51
|
+
- lib/m4dbi.rb
|
53
52
|
- lib/m4dbi/row.rb
|
54
|
-
- lib/m4dbi/
|
53
|
+
- lib/m4dbi/array.rb
|
55
54
|
- lib/m4dbi/traits.rb
|
56
|
-
- lib/m4dbi/
|
55
|
+
- lib/m4dbi/collection.rb
|
57
56
|
- lib/m4dbi/hash.rb
|
57
|
+
- lib/m4dbi/database-handle.rb
|
58
58
|
- lib/m4dbi/model.rb
|
59
|
-
- lib/m4dbi.rb
|
60
|
-
- spec/model.rb
|
59
|
+
- lib/m4dbi/timestamp.rb
|
61
60
|
- spec/dbi.rb
|
62
61
|
- spec/hash.rb
|
62
|
+
- spec/model.rb
|
63
63
|
- spec/helper.rb
|
64
|
-
has_rdoc:
|
64
|
+
has_rdoc: true
|
65
65
|
homepage: http://purepistos.net/m4dbi
|
66
|
+
licenses: []
|
67
|
+
|
66
68
|
post_install_message:
|
67
69
|
rdoc_options: []
|
68
70
|
|
@@ -83,12 +85,12 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
83
85
|
requirements:
|
84
86
|
- bacon (optional)
|
85
87
|
rubyforge_project: m4dbi
|
86
|
-
rubygems_version: 1.3.
|
88
|
+
rubygems_version: 1.3.4
|
87
89
|
signing_key:
|
88
|
-
specification_version:
|
90
|
+
specification_version: 3
|
89
91
|
summary: Models (and More) for DBI
|
90
92
|
test_files:
|
91
|
-
- spec/model.rb
|
92
93
|
- spec/dbi.rb
|
93
94
|
- spec/hash.rb
|
95
|
+
- spec/model.rb
|
94
96
|
- spec/helper.rb
|