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/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
|