m4dbi 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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/CHANGELOG
ADDED
data/HIM
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
== M4DBI - Models (and more) For DBI
|
2
|
+
|
3
|
+
http://purepistos.net/m4dbi
|
4
|
+
|
5
|
+
M4DBI is a Ruby library that provides ORM modelling to the Ruby DBI package
|
6
|
+
( http://ruby-dbi.rubyforge.org/ ).
|
7
|
+
|
8
|
+
=== Dependencies
|
9
|
+
|
10
|
+
- dbi (of course) http://ruby-dbi.rubyforge.org/
|
11
|
+
- DBDs for your choice of DBs
|
12
|
+
- metaid (gem install metaid)
|
13
|
+
- bacon (optional, for running tests; gem install bacon)
|
14
|
+
|
15
|
+
=== Installation
|
16
|
+
|
17
|
+
==== Nightly Gem
|
18
|
+
|
19
|
+
wget http://rome.purepistos.net/m4dbi/m4dbi-nightly.gem
|
20
|
+
gem install m4dbi-nightly.gem
|
21
|
+
|
22
|
+
==== Repository
|
23
|
+
|
24
|
+
To use the repository version, you need Subversion (http://subversion.tigris.org).
|
25
|
+
Chances are, there is a Subversion package for your Linux/UNIX flavour.
|
26
|
+
|
27
|
+
cd /where/you/want/m4dbi
|
28
|
+
svn co http://rome.purepistos.net/svn/m4dbi
|
29
|
+
|
30
|
+
Change the following to whatever the equivalent paths are for your system:
|
31
|
+
|
32
|
+
cd /usr/lib/ruby/site_ruby/1.8
|
33
|
+
ln -s /path/to/checked/out/m4dbi/trunk/lib/m4dbi
|
34
|
+
ln -s /path/to/checked/out/m4dbi/trunk/lib/m4dbi.rb
|
35
|
+
|
36
|
+
=== Usage
|
37
|
+
|
38
|
+
See http://rome.purepistos.net/m4dbi/examples/ . These are automatically
|
39
|
+
generated from the spec files under the spec/ dir.
|
40
|
+
|
41
|
+
=== Source Code
|
42
|
+
|
43
|
+
Browse source at http://rome.purepistos.net/issues/m4dbi/browser/trunk .
|
44
|
+
See coverage at http://rome.purepistos.net/m4dbi/rcov .
|
45
|
+
Very limited rdocs at http://rome.purepistos.net/m4dbi/rdoc .
|
46
|
+
|
47
|
+
=== Feedback and Support
|
48
|
+
|
49
|
+
On IRC: irc.freenode.net ##mathetes or ##ramaze .
|
50
|
+
Use http://mibbit.com if you don't have an IRC client.
|
data/READHIM
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
Don't read ME; read HIM!
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module DBI
|
2
|
+
class Collection
|
3
|
+
def initialize( the_one, the_many_model, the_one_fk )
|
4
|
+
@the_one = the_one
|
5
|
+
@the_many_model = the_many_model
|
6
|
+
@the_one_fk = the_one_fk
|
7
|
+
end
|
8
|
+
|
9
|
+
def elements
|
10
|
+
@the_many_model.where( @the_one_fk => @the_one.pk )
|
11
|
+
end
|
12
|
+
alias copy elements
|
13
|
+
|
14
|
+
def method_missing( method, *args, &blk )
|
15
|
+
elements.send( method, *args, &blk )
|
16
|
+
end
|
17
|
+
|
18
|
+
def push( new_item_hash )
|
19
|
+
new_item_hash[ @the_one_fk ] = @the_one.pk
|
20
|
+
@the_many_model.create( new_item_hash )
|
21
|
+
end
|
22
|
+
alias << push
|
23
|
+
alias add push
|
24
|
+
|
25
|
+
def delete( arg )
|
26
|
+
case arg
|
27
|
+
when @the_many_model
|
28
|
+
result = @the_many_model.dbh.do(
|
29
|
+
%{
|
30
|
+
DELETE FROM #{@the_many_model.table}
|
31
|
+
WHERE
|
32
|
+
#{@the_one_fk} = ?
|
33
|
+
AND #{@the_many_model.pk} = ?
|
34
|
+
},
|
35
|
+
@the_one.pk,
|
36
|
+
arg.pk
|
37
|
+
)
|
38
|
+
result > 0
|
39
|
+
when Hash
|
40
|
+
hash = arg
|
41
|
+
keys = hash.keys
|
42
|
+
where_subclause = keys.map { |k|
|
43
|
+
"#{k} = ?"
|
44
|
+
}.join( " AND " )
|
45
|
+
@the_many_model.dbh.do(
|
46
|
+
%{
|
47
|
+
DELETE FROM #{@the_many_model.table}
|
48
|
+
WHERE
|
49
|
+
#{@the_one_fk} = ?
|
50
|
+
AND #{where_subclause}
|
51
|
+
},
|
52
|
+
@the_one.pk,
|
53
|
+
*( keys.map { |k| hash[ k ] } )
|
54
|
+
)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Returns the number of records deleted
|
59
|
+
def clear
|
60
|
+
@the_many_model.dbh.do(
|
61
|
+
%{
|
62
|
+
DELETE FROM #{@the_many_model.table}
|
63
|
+
WHERE #{@the_one_fk} = ?
|
64
|
+
},
|
65
|
+
@the_one.pk
|
66
|
+
)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'dbi'
|
2
|
+
require 'thread'
|
3
|
+
|
4
|
+
module DBI
|
5
|
+
|
6
|
+
# Here, we engage in some hackery to get database handles to provide us
|
7
|
+
# with the name of the database connected to. For mystical reasons, this
|
8
|
+
# is hidden in normal DBI.
|
9
|
+
# Retrieve the database name with DatabaseHandle#dbname.
|
10
|
+
module DBD; module Pg
|
11
|
+
module ConnectionDatabaseNameAccessor
|
12
|
+
def dbname
|
13
|
+
@connection.db
|
14
|
+
end
|
15
|
+
end
|
16
|
+
module DatabaseNameAccessor
|
17
|
+
def dbname
|
18
|
+
@handle.dbname
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end; end
|
22
|
+
|
23
|
+
class DatabaseHandle
|
24
|
+
alias old_initialize initialize
|
25
|
+
def initialize( handle )
|
26
|
+
DBI::DatabaseHandle.last_handle = self
|
27
|
+
handle = old_initialize( handle )
|
28
|
+
@mutex = Mutex.new
|
29
|
+
|
30
|
+
# Hackery to expose dbname.
|
31
|
+
if defined?( DBI::DBD::Pg::Database ) and ( DBI::DBD::Pg::Database === @handle )
|
32
|
+
@handle.extend DBI::DBD::Pg::ConnectionDatabaseNameAccessor
|
33
|
+
extend DBI::DBD::Pg::DatabaseNameAccessor
|
34
|
+
end
|
35
|
+
# TODO: more DBDs
|
36
|
+
|
37
|
+
handle
|
38
|
+
end
|
39
|
+
|
40
|
+
# Atomically disable autocommit, do transaction, and reenable.
|
41
|
+
# Used for a single transaction when autocommit is normally left on.
|
42
|
+
# Only one thread can execute one_transaction at a time,
|
43
|
+
# since we need to thread protect the AutoCommit property of the
|
44
|
+
# database handle.
|
45
|
+
def one_transaction
|
46
|
+
@mutex.synchronize do
|
47
|
+
auto_commit = self[ 'AutoCommit' ]
|
48
|
+
self[ 'AutoCommit' ] = false
|
49
|
+
result = transaction do
|
50
|
+
yield self
|
51
|
+
end
|
52
|
+
self[ 'AutoCommit' ] = auto_commit
|
53
|
+
result
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def select_column( statement, *bindvars )
|
58
|
+
row = select_one( statement, *bindvars )
|
59
|
+
if row
|
60
|
+
row[ 0 ]
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
alias s select_all
|
65
|
+
alias s1 select_one
|
66
|
+
alias sc select_column
|
67
|
+
alias u do
|
68
|
+
alias i do
|
69
|
+
alias d do
|
70
|
+
|
71
|
+
class << self
|
72
|
+
def last_handle
|
73
|
+
@handle# ||= create_handle
|
74
|
+
end
|
75
|
+
|
76
|
+
def last_handle=( handle )
|
77
|
+
@handle = handle
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
|
85
|
+
|
86
|
+
|
data/lib/m4dbi/hash.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
class Hash
|
2
|
+
def to_clause( join_string )
|
3
|
+
# The clause items and the values have to be in the same order.
|
4
|
+
keys_ = keys
|
5
|
+
clause = keys_.map { |field|
|
6
|
+
"#{field} = ?"
|
7
|
+
}.join( join_string )
|
8
|
+
values_ = keys_.map { |key|
|
9
|
+
self[ key ]
|
10
|
+
}
|
11
|
+
[ clause, values_ ]
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_where_clause
|
15
|
+
to_clause( " AND " )
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_set_clause
|
19
|
+
to_clause( ", " )
|
20
|
+
end
|
21
|
+
end
|
data/lib/m4dbi/model.rb
ADDED
@@ -0,0 +1,372 @@
|
|
1
|
+
require 'dbi'
|
2
|
+
require 'metaid'
|
3
|
+
|
4
|
+
module DBI
|
5
|
+
class Model
|
6
|
+
#attr_reader :row
|
7
|
+
ancestral_trait_reader :dbh, :table
|
8
|
+
ancestral_trait_class_reader :dbh, :table, :pk, :columns
|
9
|
+
|
10
|
+
M4DBI_UNASSIGNED = '__m4dbi_unassigned__'
|
11
|
+
|
12
|
+
extend Enumerable
|
13
|
+
|
14
|
+
def self.[]( hash_or_pk_value )
|
15
|
+
case hash_or_pk_value
|
16
|
+
when Hash
|
17
|
+
clause, values = hash_or_pk_value.to_where_clause
|
18
|
+
row = dbh.select_one(
|
19
|
+
"SELECT * FROM #{table} WHERE #{clause}",
|
20
|
+
*values
|
21
|
+
)
|
22
|
+
else
|
23
|
+
row = dbh.select_one(
|
24
|
+
"SELECT * FROM #{table} WHERE #{pk} = ?",
|
25
|
+
hash_or_pk_value
|
26
|
+
)
|
27
|
+
end
|
28
|
+
|
29
|
+
if row
|
30
|
+
self.new( row )
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.from_rows( rows )
|
35
|
+
rows.map { |r| self.new( r ) }
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.where( conditions, *args )
|
39
|
+
case conditions
|
40
|
+
when String
|
41
|
+
sql = "SELECT * FROM #{table} WHERE #{conditions}"
|
42
|
+
params = args
|
43
|
+
when Hash
|
44
|
+
cond, params = conditions.to_where_clause
|
45
|
+
sql = "SELECT * FROM #{table} WHERE #{cond}"
|
46
|
+
end
|
47
|
+
|
48
|
+
self.from_rows(
|
49
|
+
dbh.select_all( sql, *params )
|
50
|
+
)
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.one_where( conditions, *args )
|
54
|
+
case conditions
|
55
|
+
when String
|
56
|
+
sql = "SELECT * FROM #{table} WHERE #{conditions} LIMIT 1"
|
57
|
+
params = args
|
58
|
+
when Hash
|
59
|
+
cond, params = conditions.to_where_clause
|
60
|
+
sql = "SELECT * FROM #{table} WHERE #{cond} LIMIT 1"
|
61
|
+
end
|
62
|
+
|
63
|
+
row = dbh.select_one( sql, *params )
|
64
|
+
if row
|
65
|
+
self.new( row )
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.all
|
70
|
+
self.from_rows(
|
71
|
+
dbh.select_all( "SELECT * FROM #{table}" )
|
72
|
+
)
|
73
|
+
end
|
74
|
+
|
75
|
+
# TODO: Perhaps we'll use cursors for Model#each.
|
76
|
+
def self.each( &block )
|
77
|
+
self.all.each( &block )
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.one
|
81
|
+
row = dbh.select_one( "SELECT * FROM #{table} LIMIT 1" )
|
82
|
+
if row
|
83
|
+
self.new( row )
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def self.count
|
88
|
+
dbh.select_column( "SELECT COUNT(*) FROM #{table}" )
|
89
|
+
end
|
90
|
+
|
91
|
+
def self.create( hash = {} )
|
92
|
+
if block_given?
|
93
|
+
row = DBI::Row.new(
|
94
|
+
columns.collect { |c| c[ 'name' ] },
|
95
|
+
[ M4DBI_UNASSIGNED ] * columns.size
|
96
|
+
)
|
97
|
+
yield row
|
98
|
+
hash = row.to_h
|
99
|
+
hash.to_a.each do |key,value|
|
100
|
+
if value == M4DBI_UNASSIGNED
|
101
|
+
hash.delete( key )
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
keys = hash.keys
|
107
|
+
cols = keys.join( ',' )
|
108
|
+
values = keys.map { |key| hash[ key ] }
|
109
|
+
value_placeholders = values.map { |v| '?' }.join( ',' )
|
110
|
+
rec = nil
|
111
|
+
|
112
|
+
dbh.one_transaction do |dbh_|
|
113
|
+
num_inserted = dbh_.do(
|
114
|
+
"INSERT INTO #{table} ( #{cols} ) VALUES ( #{value_placeholders} )",
|
115
|
+
*values
|
116
|
+
)
|
117
|
+
if num_inserted > 0
|
118
|
+
pk_value = hash[ self.pk.to_sym ] || hash[ self.pk.to_s ]
|
119
|
+
if pk_value
|
120
|
+
rec = self.one_where( self.pk => pk_value )
|
121
|
+
else
|
122
|
+
begin
|
123
|
+
rec = last_record( dbh_ )
|
124
|
+
rescue NoMethodError => e
|
125
|
+
# ignore
|
126
|
+
#puts "not implemented: #{e.message}"
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
rec
|
133
|
+
end
|
134
|
+
|
135
|
+
def self.find_or_create( hash = nil )
|
136
|
+
item = nil
|
137
|
+
error = nil
|
138
|
+
item = self.one_where( hash )
|
139
|
+
if item.nil?
|
140
|
+
item =
|
141
|
+
begin
|
142
|
+
self.create( hash )
|
143
|
+
rescue => error
|
144
|
+
self.one_where( hash )
|
145
|
+
end
|
146
|
+
end
|
147
|
+
if item
|
148
|
+
item
|
149
|
+
else
|
150
|
+
raise error
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def self.select_all( *args )
|
155
|
+
self.from_rows(
|
156
|
+
dbh.select_all( *args )
|
157
|
+
)
|
158
|
+
end
|
159
|
+
|
160
|
+
def self.select_one( *args )
|
161
|
+
row = dbh.select_one( *args )
|
162
|
+
if row
|
163
|
+
self.new( row )
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
class << self
|
168
|
+
alias s select_all
|
169
|
+
alias s1 select_one
|
170
|
+
end
|
171
|
+
|
172
|
+
def self.update( where_hash_or_clause, set_hash )
|
173
|
+
where_clause = nil
|
174
|
+
set_clause = nil
|
175
|
+
where_params = nil
|
176
|
+
|
177
|
+
if where_hash_or_clause.respond_to? :keys
|
178
|
+
where_clause, where_params = where_hash_or_clause.to_where_clause
|
179
|
+
else
|
180
|
+
where_clause = where_hash_or_clause
|
181
|
+
where_params = []
|
182
|
+
end
|
183
|
+
|
184
|
+
set_clause, set_params = set_hash.to_set_clause
|
185
|
+
params = set_params + where_params
|
186
|
+
dbh.do(
|
187
|
+
"UPDATE #{table} SET #{set_clause} WHERE #{where_clause}",
|
188
|
+
*params
|
189
|
+
)
|
190
|
+
end
|
191
|
+
|
192
|
+
def self.update_one( pk_value, set_hash )
|
193
|
+
set_clause, set_params = set_hash.to_set_clause
|
194
|
+
params = set_params + [ pk_value ]
|
195
|
+
dbh.do(
|
196
|
+
"UPDATE #{table} SET #{set_clause} WHERE #{pk} = ?",
|
197
|
+
*params
|
198
|
+
)
|
199
|
+
end
|
200
|
+
|
201
|
+
# Example:
|
202
|
+
# DBI::Model.one_to_many( Author, :posts, Post, :author, :author_id )
|
203
|
+
# her_posts = some_author.posts
|
204
|
+
# the_author = some_post.author
|
205
|
+
def self.one_to_many( the_one, the_many, many_as, one_as, the_one_fk )
|
206
|
+
the_one.class_def( many_as.to_sym ) do
|
207
|
+
DBI::Collection.new( self, the_many, the_one_fk )
|
208
|
+
end
|
209
|
+
the_many.class_def( one_as.to_sym ) do
|
210
|
+
the_one[ @row[ the_one_fk ] ]
|
211
|
+
end
|
212
|
+
the_many.class_def( "#{one_as}=".to_sym ) do |new_one|
|
213
|
+
send( "#{the_one_fk}=".to_sym, new_one.pk )
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
# Example:
|
218
|
+
# DBI::Model.many_to_many(
|
219
|
+
# @m_author, @m_fan, :authors_liked, :fans, :authors_fans, :author_id, :fan_id
|
220
|
+
# )
|
221
|
+
# her_fans = some_author.fans
|
222
|
+
# favourite_authors = fan.authors_liked
|
223
|
+
def self.many_to_many( model1, model2, m1_as, m2_as, join_table, m1_fk, m2_fk )
|
224
|
+
model1.class_def( m2_as.to_sym ) do
|
225
|
+
model2.select_all(
|
226
|
+
%{
|
227
|
+
SELECT
|
228
|
+
m2.*
|
229
|
+
FROM
|
230
|
+
#{model2.table} m2,
|
231
|
+
#{join_table} j
|
232
|
+
WHERE
|
233
|
+
j.#{m1_fk} = ?
|
234
|
+
AND m2.id = j.#{m2_fk}
|
235
|
+
},
|
236
|
+
pk
|
237
|
+
)
|
238
|
+
end
|
239
|
+
|
240
|
+
model2.class_def( m1_as.to_sym ) do
|
241
|
+
model1.select_all(
|
242
|
+
%{
|
243
|
+
SELECT
|
244
|
+
m1.*
|
245
|
+
FROM
|
246
|
+
#{model1.table} m1,
|
247
|
+
#{join_table} j
|
248
|
+
WHERE
|
249
|
+
j.#{m2_fk} = ?
|
250
|
+
AND m1.id = j.#{m1_fk}
|
251
|
+
},
|
252
|
+
pk
|
253
|
+
)
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
# ------------------- :nodoc:
|
258
|
+
|
259
|
+
def initialize( row )
|
260
|
+
if not row.respond_to?( "[]".to_sym ) or not row.respond_to?( "[]=".to_sym )
|
261
|
+
raise DBI::Error.new( "Attempted to instantiate DBI::Model with an invalid argument (#{row.inspect}). (Expecting DBI::Row.)" )
|
262
|
+
end
|
263
|
+
if caller[ 1 ] !~ %r{/m4dbi/model\.rb:}
|
264
|
+
warn "Do not call DBI::Model#new directly; use DBI::Model#create instead."
|
265
|
+
end
|
266
|
+
@row = row
|
267
|
+
end
|
268
|
+
|
269
|
+
def method_missing( method, *args )
|
270
|
+
@row.send( method, *args )
|
271
|
+
end
|
272
|
+
|
273
|
+
def pk
|
274
|
+
@row[ self.class.pk ]
|
275
|
+
end
|
276
|
+
|
277
|
+
def pk_column
|
278
|
+
self.class.pk
|
279
|
+
end
|
280
|
+
|
281
|
+
def ==( other )
|
282
|
+
other and ( pk == other.pk )
|
283
|
+
end
|
284
|
+
|
285
|
+
def hash
|
286
|
+
"#{self.class.hash}#{pk}".to_i
|
287
|
+
end
|
288
|
+
|
289
|
+
def eql?( other )
|
290
|
+
hash == other.hash
|
291
|
+
end
|
292
|
+
|
293
|
+
def set( hash )
|
294
|
+
set_clause, set_params = hash.to_set_clause
|
295
|
+
set_params << pk
|
296
|
+
dbh.do(
|
297
|
+
"UPDATE #{table} SET #{set_clause} WHERE #{pk_column} = ?",
|
298
|
+
*set_params
|
299
|
+
)
|
300
|
+
end
|
301
|
+
|
302
|
+
# Returns true iff the record and only the record was successfully deleted.
|
303
|
+
def delete
|
304
|
+
num_deleted = dbh.do(
|
305
|
+
"DELETE FROM #{table} WHERE #{pk_column} = ?",
|
306
|
+
pk
|
307
|
+
)
|
308
|
+
num_deleted == 1
|
309
|
+
end
|
310
|
+
|
311
|
+
# save does nothing. It exists to provide compatibility with other ORMs.
|
312
|
+
def save
|
313
|
+
nil
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
# Define a new DBI::Model like this:
|
318
|
+
# class Post < DBI::Model( :posts ); end
|
319
|
+
# You can specify the primary key column like so:
|
320
|
+
# class Author < DBI::Model( :authors, 'id' ); end
|
321
|
+
def self.Model( table, pk_ = 'id' )
|
322
|
+
h = DBI::DatabaseHandle.last_handle
|
323
|
+
if h.nil? or not h.connected?
|
324
|
+
raise DBI::Error.new( "Attempted to create a Model class without first connecting to a database." )
|
325
|
+
end
|
326
|
+
|
327
|
+
model_key =
|
328
|
+
if defined?( DBI::DBD::Pg::Database ) and DBI::DBD::Pg::Database === h.handle
|
329
|
+
"#{h.dbname}::#{table}"
|
330
|
+
# TODO: more DBDs
|
331
|
+
else
|
332
|
+
table
|
333
|
+
end
|
334
|
+
|
335
|
+
@models ||= Hash.new
|
336
|
+
@models[ model_key ] ||= Class.new( DBI::Model ) do |klass|
|
337
|
+
klass.trait( {
|
338
|
+
:dbh => h,
|
339
|
+
:table => table,
|
340
|
+
:pk => pk_,
|
341
|
+
:columns => h.columns( table.to_s ),
|
342
|
+
} )
|
343
|
+
|
344
|
+
if defined?( DBI::DBD::Pg::Database ) and DBI::DBD::Pg::Database === h.handle
|
345
|
+
meta_def( "last_record".to_sym ) do |dbh_|
|
346
|
+
self.s1 "SELECT * FROM #{table} WHERE #{pk} = currval( '#{table}_#{pk}_seq' );"
|
347
|
+
end
|
348
|
+
# TODO: more DBDs
|
349
|
+
end
|
350
|
+
|
351
|
+
klass.trait[ :columns ].each do |col|
|
352
|
+
colname = col[ 'name' ]
|
353
|
+
|
354
|
+
class_def( colname.to_sym ) do
|
355
|
+
@row[ colname ]
|
356
|
+
end
|
357
|
+
|
358
|
+
class_def( "#{colname}=".to_sym ) do |new_value|
|
359
|
+
num_changed = dbh.do(
|
360
|
+
"UPDATE #{table} SET #{colname} = ? WHERE #{pk_column} = ?",
|
361
|
+
new_value,
|
362
|
+
pk
|
363
|
+
)
|
364
|
+
if num_changed > 0
|
365
|
+
@row[ colname ] = new_value
|
366
|
+
end
|
367
|
+
end
|
368
|
+
end
|
369
|
+
end
|
370
|
+
end
|
371
|
+
|
372
|
+
end
|
data/lib/m4dbi/row.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'dbi'
|
2
|
+
|
3
|
+
module DBI
|
4
|
+
class Row
|
5
|
+
def method_missing( method, *args )
|
6
|
+
if method.to_s =~ /^(.+)=$/
|
7
|
+
field = $1
|
8
|
+
if not @column_names.include?( field )
|
9
|
+
field = convert_alternate_fieldname( field )
|
10
|
+
end
|
11
|
+
if @column_names.include?( field )
|
12
|
+
self[ field ] = *args
|
13
|
+
else
|
14
|
+
super
|
15
|
+
end
|
16
|
+
else
|
17
|
+
field = method.to_s
|
18
|
+
# We shouldn't use by_field directly and test for nil,
|
19
|
+
# because nil may be a valid value for the column.
|
20
|
+
if not @column_names.include?( field )
|
21
|
+
field = convert_alternate_fieldname( field )
|
22
|
+
end
|
23
|
+
if @column_names.include?( field )
|
24
|
+
by_field field
|
25
|
+
else
|
26
|
+
super
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def convert_alternate_fieldname( field )
|
32
|
+
field.gsub( /(^_)|(_$)/ , '' )
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|