m4dbi 0.7.3 → 0.8.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/README.rdoc ADDED
@@ -0,0 +1,39 @@
1
+ == M4DBI - Models (and more) For DBI
2
+
3
+ https://github.com/Pistos/m4dbi
4
+
5
+ M4DBI is a Ruby library that provides ORM modelling to RDBI
6
+ ( https://github.com/RDBI/rdbi ).
7
+
8
+ === Dependencies
9
+
10
+ - rdbi
11
+ - rdbi-driver-* for your choice of DBs
12
+ - metaid (gem install metaid)
13
+ - bacon (optional, for running tests; gem install bacon)
14
+
15
+ === Installation
16
+
17
+ ==== Repository
18
+
19
+ To use the repository version, you need git ( http://git-scm.com/ ).
20
+ Chances are, there is a git package for your Linux/UNIX flavour.
21
+
22
+ cd /where/you/want/m4dbi
23
+ git clone git://github.com/Pistos/m4dbi.git
24
+
25
+ === Usage
26
+
27
+ See the spec/ dir.
28
+
29
+ (Somewhat out of date -- 2011-08-14)
30
+
31
+ === Source Code
32
+
33
+ Browse source at http://github.com/Pistos/m4dbi .
34
+ Very limited rdocs at http://rdoc.info/github/Pistos/m4dbi/master/frames .
35
+
36
+ === Feedback and Support
37
+
38
+ On IRC: irc.freenode.net ##mathetes .
39
+ Use http://webchat.freenode.net/?channels=mathetes if you don't have an IRC client.
@@ -25,13 +25,15 @@ module M4DBI
25
25
  def delete( arg )
26
26
  case arg
27
27
  when @the_many_model
28
- @the_many_model.dbh.execute(
28
+ stm = @the_many_model.dbh.prepare(
29
29
  %{
30
30
  DELETE FROM #{@the_many_model.table}
31
31
  WHERE
32
32
  #{@the_one_fk} = ?
33
33
  AND #{@the_many_model.pk_clause}
34
- },
34
+ }
35
+ )
36
+ stm.execute(
35
37
  @the_one.pk,
36
38
  arg.pk
37
39
  ).affected_count > 0
@@ -41,13 +43,15 @@ module M4DBI
41
43
  where_subclause = keys.map { |k|
42
44
  "#{k} = ?"
43
45
  }.join( " AND " )
44
- @the_many_model.dbh.execute(
46
+ stm = @the_many_model.dbh.prepare(
45
47
  %{
46
48
  DELETE FROM #{@the_many_model.table}
47
49
  WHERE
48
50
  #{@the_one_fk} = ?
49
51
  AND #{where_subclause}
50
- },
52
+ }
53
+ )
54
+ stm.execute(
51
55
  @the_one.pk,
52
56
  *( keys.map { |k| hash[ k ] } )
53
57
  ).affected_count
@@ -56,13 +60,13 @@ module M4DBI
56
60
 
57
61
  # Returns the number of records deleted
58
62
  def clear
59
- @the_many_model.dbh.execute(
63
+ stm = @the_many_model.dbh.prepare(
60
64
  %{
61
65
  DELETE FROM #{@the_many_model.table}
62
66
  WHERE #{@the_one_fk} = ?
63
- },
64
- @the_one.pk
65
- ).affected_count
67
+ }
68
+ )
69
+ stm.execute( @the_one.pk ).affected_count
66
70
  end
67
71
  end
68
- end
72
+ end
@@ -6,6 +6,10 @@ module M4DBI
6
6
  @dbh = rdbi_dbh
7
7
  end
8
8
 
9
+ def prepare( *args )
10
+ Statement.new( @dbh.prepare(*args) )
11
+ end
12
+
9
13
  def execute( *args )
10
14
  @dbh.execute *args
11
15
  end
data/lib/m4dbi/model.rb CHANGED
@@ -2,12 +2,16 @@ module M4DBI
2
2
  class Model
3
3
  #attr_reader :row
4
4
  ancestral_trait_reader :dbh, :table
5
- ancestral_trait_class_reader :dbh, :table, :pk, :columns
5
+ ancestral_trait_class_reader :dbh, :table, :pk, :columns, :st
6
6
 
7
7
  M4DBI_UNASSIGNED = '__m4dbi_unassigned__'
8
8
 
9
9
  extend Enumerable
10
10
 
11
+ def self.prepare( sql )
12
+ st[sql] ||= dbh.prepare(sql)
13
+ end
14
+
11
15
  def self.[]( first_arg, *args )
12
16
  if args.size == 0
13
17
  case first_arg
@@ -25,10 +29,9 @@ module M4DBI
25
29
  values = [ first_arg ] + args
26
30
  end
27
31
 
28
- row = dbh.select_one(
29
- "SELECT * FROM #{table} WHERE #{clause}",
30
- *values
31
- )
32
+ sql = "SELECT * FROM #{table} WHERE #{clause}"
33
+ stm = prepare(sql)
34
+ row = stm.select_one(*values)
32
35
 
33
36
  if row
34
37
  self.new( row )
@@ -36,9 +39,9 @@ module M4DBI
36
39
  end
37
40
 
38
41
  def self.pk_clause
39
- pk.map { |col|
40
- "#{col} = ?"
41
- }.join( ' AND ' )
42
+ pk.
43
+ map { |col| "#{col} = ?" }.
44
+ join( ' AND ' )
42
45
  end
43
46
 
44
47
  def self.from_rows( rows )
@@ -55,8 +58,9 @@ module M4DBI
55
58
  sql = "SELECT * FROM #{table} WHERE #{cond}"
56
59
  end
57
60
 
61
+ stm = prepare(sql)
58
62
  self.from_rows(
59
- dbh.select_all( sql, *params )
63
+ stm.select_all(*params)
60
64
  )
61
65
  end
62
66
 
@@ -70,16 +74,16 @@ module M4DBI
70
74
  sql = "SELECT * FROM #{table} WHERE #{cond} LIMIT 1"
71
75
  end
72
76
 
73
- row = dbh.select_one( sql, *params )
77
+ stm = prepare(sql)
78
+ row = stm.select_one( *params )
74
79
  if row
75
80
  self.new( row )
76
81
  end
77
82
  end
78
83
 
79
84
  def self.all
80
- self.from_rows(
81
- dbh.select_all( "SELECT * FROM #{table}" )
82
- )
85
+ stm = prepare("SELECT * FROM #{table}")
86
+ self.from_rows( stm.select_all )
83
87
  end
84
88
 
85
89
  # TODO: Perhaps we'll use cursors for Model#each.
@@ -88,14 +92,16 @@ module M4DBI
88
92
  end
89
93
 
90
94
  def self.one
91
- row = dbh.select_one( "SELECT * FROM #{table} LIMIT 1" )
95
+ stm = prepare("SELECT * FROM #{table} LIMIT 1")
96
+ row = stm.select_one
92
97
  if row
93
98
  self.new( row )
94
99
  end
95
100
  end
96
101
 
97
102
  def self.count
98
- dbh.select_column( "SELECT COUNT(*) FROM #{table}" ).to_i
103
+ stm = prepare("SELECT COUNT(*) FROM #{table}")
104
+ stm.select_column.to_i
99
105
  end
100
106
 
101
107
  def self.create( hash = {} )
@@ -123,7 +129,8 @@ module M4DBI
123
129
  else
124
130
  sql = "INSERT INTO #{table} ( #{cols} ) VALUES ( #{value_placeholders} )"
125
131
  end
126
- num_inserted = dbh_.execute( sql, *values ).affected_count
132
+ stm = prepare(sql)
133
+ num_inserted = stm.execute(*values).affected_count
127
134
  if num_inserted > 0
128
135
  pk_hash = hash.slice( *(
129
136
  self.pk.map { |pk_col| pk_col.to_sym }
@@ -133,7 +140,7 @@ module M4DBI
133
140
  self.pk.map { |pk_col| pk_col.to_s }
134
141
  ) )
135
142
  end
136
- if not pk_hash.empty?
143
+ if ! pk_hash.empty?
137
144
  rec = self.one_where( pk_hash )
138
145
  else
139
146
  begin
@@ -168,14 +175,16 @@ module M4DBI
168
175
  end
169
176
  end
170
177
 
171
- def self.select_all( *args )
178
+ def self.select_all( sql, *binds )
179
+ stm = prepare(sql)
172
180
  self.from_rows(
173
- dbh.select_all( *args )
181
+ stm.select_all( *binds )
174
182
  )
175
183
  end
176
184
 
177
- def self.select_one( *args )
178
- row = dbh.select_one( *args )
185
+ def self.select_one( sql, *binds )
186
+ stm = prepare(sql)
187
+ row = stm.select_one( *binds )
179
188
  if row
180
189
  self.new( row )
181
190
  end
@@ -200,20 +209,16 @@ module M4DBI
200
209
 
201
210
  set_clause, set_params = set_hash.to_set_clause
202
211
  params = set_params + where_params
203
- dbh.execute(
204
- "UPDATE #{table} SET #{set_clause} WHERE #{where_clause}",
205
- *params
206
- )
212
+ stm = prepare("UPDATE #{table} SET #{set_clause} WHERE #{where_clause}")
213
+ stm.execute( *params )
207
214
  end
208
215
 
209
216
  def self.update_one( *args )
210
217
  set_clause, set_params = args[ -1 ].to_set_clause
211
218
  pk_values = args[ 0..-2 ]
212
219
  params = set_params + pk_values
213
- dbh.execute(
214
- "UPDATE #{table} SET #{set_clause} WHERE #{pk_clause}",
215
- *params
216
- )
220
+ stm = prepare("UPDATE #{table} SET #{set_clause} WHERE #{pk_clause}")
221
+ stm.execute( *params )
217
222
  end
218
223
 
219
224
  # Example:
@@ -278,12 +283,17 @@ module M4DBI
278
283
 
279
284
  def initialize( row )
280
285
  if ! row.respond_to?( "[]".to_sym ) || ! row.respond_to?( "[]=".to_sym )
281
- raise M4DBI::Error.new( "Attempted to instantiate M4DBI::Model with an invalid argument (#{row.inspect}). (Expecting something accessible [] and []= .)" )
286
+ raise M4DBI::Error.new( "Attempted to instantiate M4DBI::Model with an invalid argument (#{row.inspect}). (Expecting something accessible with [] and []= .)" )
282
287
  end
283
288
  if caller[ 1 ] !~ %r{/m4dbi/model\.rb:}
284
289
  warn "Do not call M4DBI::Model#new directly; use M4DBI::Model#create instead."
285
290
  end
286
291
  @row = row
292
+ @st = Hash.new
293
+ end
294
+
295
+ def prepare( sql )
296
+ @st[sql] ||= dbh.prepare(sql)
287
297
  end
288
298
 
289
299
  def method_missing( method, *args )
@@ -353,10 +363,8 @@ module M4DBI
353
363
  def set( hash )
354
364
  set_clause, set_params = hash.to_set_clause
355
365
  set_params << pk
356
- num_updated = dbh.execute(
357
- "UPDATE #{table} SET #{set_clause} WHERE #{pk_clause}",
358
- *set_params
359
- ).affected_count
366
+ st = prepare("UPDATE #{table} SET #{set_clause} WHERE #{pk_clause}")
367
+ num_updated = st.execute( *set_params ).affected_count
360
368
  if num_updated > 0
361
369
  hash.each do |key,value|
362
370
  @row[ key ] = value
@@ -367,10 +375,8 @@ module M4DBI
367
375
 
368
376
  # Returns true iff the record and only the record was successfully deleted.
369
377
  def delete
370
- num_deleted = dbh.execute(
371
- "DELETE FROM #{table} WHERE #{pk_clause}",
372
- *pk_values
373
- ).affected_count
378
+ st = prepare("DELETE FROM #{table} WHERE #{pk_clause}")
379
+ num_deleted = st.execute( *pk_values ).affected_count
374
380
  num_deleted == 1
375
381
  end
376
382
 
@@ -404,10 +410,11 @@ module M4DBI
404
410
  @models ||= Hash.new
405
411
  @models[ model_key ] ||= Class.new( M4DBI::Model ) do |klass|
406
412
  klass.trait( {
407
- :dbh => h,
408
- :table => table,
409
- :pk => pk_,
413
+ :dbh => h,
414
+ :table => table,
415
+ :pk => pk_,
410
416
  :columns => h.table_schema( table.to_sym ).columns,
417
+ :st => Hash.new, # prepared statements for all queries
411
418
  } )
412
419
 
413
420
  meta_def( 'pk_str'.to_sym ) do
@@ -450,8 +457,8 @@ module M4DBI
450
457
  # Column writers
451
458
 
452
459
  class_def( "#{method}=".to_sym ) do |new_value|
453
- num_changed = dbh.execute(
454
- "UPDATE #{table} SET #{colname} = ? WHERE #{pk_clause}",
460
+ stm = prepare("UPDATE #{table} SET #{colname} = ? WHERE #{pk_clause}")
461
+ num_changed = stm.execute(
455
462
  new_value,
456
463
  *pk_values
457
464
  ).affected_count
@@ -461,8 +468,8 @@ module M4DBI
461
468
  end
462
469
 
463
470
  class_def( '[]='.to_sym ) do |colname, new_value|
464
- num_changed = dbh.execute(
465
- "UPDATE #{table} SET #{colname} = ? WHERE #{pk_clause}",
471
+ stm = prepare("UPDATE #{table} SET #{colname} = ? WHERE #{pk_clause}")
472
+ num_changed = stm.execute(
466
473
  new_value,
467
474
  *pk_values
468
475
  ).affected_count
@@ -0,0 +1,39 @@
1
+ module M4DBI
2
+ class Statement
3
+ def initialize( rdbi_statement )
4
+ @st = rdbi_statement
5
+ end
6
+
7
+ def execute( *args )
8
+ @st.execute *args
9
+ end
10
+
11
+ def select( *bindvars )
12
+ @st.execute( *bindvars ).fetch( :all, RDBI::Result::Driver::Struct )
13
+ end
14
+
15
+ def select_one( *bindvars )
16
+ select( *bindvars )[0]
17
+ end
18
+
19
+ def select_column( *bindvars )
20
+ rows = @st.execute( *bindvars ).fetch( 1, RDBI::Result::Driver::Array )
21
+ if rows.any?
22
+ rows[0][0]
23
+ else
24
+ raise RDBI::Error.new( "Query returned no rows. SQL: #{@dbh.last_query}" )
25
+ end
26
+ end
27
+
28
+ alias select_all select
29
+ alias s select
30
+ alias s1 select_one
31
+ alias sc select_column
32
+ alias update execute
33
+ alias u execute
34
+ alias insert execute
35
+ alias i execute
36
+ alias delete execute
37
+ alias d execute
38
+ end
39
+ end
data/lib/m4dbi/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module M4DBI
2
- VERSION = '0.7.3'
2
+ VERSION = '0.8.0'
3
3
  end
data/lib/m4dbi.rb CHANGED
@@ -11,6 +11,7 @@ require "#{__DIR__}/m4dbi/traits"
11
11
  require "#{__DIR__}/m4dbi/hash"
12
12
  require "#{__DIR__}/m4dbi/array"
13
13
  require "#{__DIR__}/m4dbi/database"
14
+ require "#{__DIR__}/m4dbi/statement"
14
15
  require "#{__DIR__}/m4dbi/model"
15
16
  require "#{__DIR__}/m4dbi/collection"
16
17
 
data/spec/helper.rb CHANGED
@@ -17,7 +17,7 @@ puts "M4DBI version: #{M4DBI::VERSION}"
17
17
  # See test-schema*.sql and test-data.sql
18
18
  def connect_to_spec_database( database = ( ENV[ 'M4DBI_DATABASE' ] || 'm4dbi' ) )
19
19
  driver = ENV[ 'M4DBI_DRIVER' ] || "PostgreSQL"
20
- # puts "\nUsing RDBI driver: '#{driver}'"
20
+ puts "\nUsing RDBI driver: '#{driver}'"
21
21
  case driver.downcase
22
22
  when 'postgresql', 'sqlite3', 'mysql'
23
23
  require "rdbi/driver/#{driver.downcase}"
data/spec/model.rb CHANGED
@@ -478,6 +478,7 @@ describe 'A M4DBI::Model subclass' do
478
478
  r.should.respond_to :time_created
479
479
  r.should.not.respond_to :no_column_by_this_name
480
480
  r.time_created.should.not.be.nil
481
+ r.time_created.should.be.kind_of DateTime
481
482
  end
482
483
 
483
484
  it 'returns a record via #find_or_create( Hash )' do
metadata CHANGED
@@ -2,7 +2,7 @@
2
2
  name: m4dbi
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 0.7.3
5
+ version: 0.8.0
6
6
  platform: ruby
7
7
  authors:
8
8
  - Pistos
@@ -10,7 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2011-08-14 00:00:00 Z
13
+ date: 2011-09-12 00:00:00 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: metaid
@@ -41,13 +41,14 @@ executables: []
41
41
  extensions: []
42
42
 
43
43
  extra_rdoc_files:
44
- - README
44
+ - README.rdoc
45
45
  - CHANGELOG
46
46
  - LICENCE
47
47
  files:
48
- - README
48
+ - README.rdoc
49
49
  - CHANGELOG
50
50
  - LICENCE
51
+ - lib/m4dbi/statement.rb
51
52
  - lib/m4dbi/database.rb
52
53
  - lib/m4dbi/model.rb
53
54
  - lib/m4dbi/version.rb
data/README DELETED
@@ -1 +0,0 @@
1
- Don't read ME; read HIM!