m4dbi 0.6.1 → 0.6.2
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 +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
|