m4dbi 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,3 +1,19 @@
1
+ ## 0.6.0
2
+
3
+ - Compatibility with DBI 0.4.0 ensured.
4
+ - Support for multi-column primary keys added.
5
+ - MySQL support improved and spec'ed.
6
+ - SQLite support improved and spec'ed.
7
+ - MIT licence adopted.
8
+
9
+ ## 0.5.5
10
+
11
+ - Improved and added numerous specs.
12
+ - More precise NoMethodError? thrown on Model instances. (r2601)
13
+ - DBI::Timestamp now behaves like Time. (r2602)
14
+ - Model instance writers now properly modify object values (not just database).
15
+ - nil values in condition clauses ([] and "where") no longer generate wrong SQL.
16
+
1
17
  ## 0.5.0
2
18
 
3
- First official public release.
19
+ - First official public release.
data/HIM CHANGED
@@ -21,17 +21,17 @@ M4DBI is a Ruby library that provides ORM modelling to the Ruby DBI package
21
21
 
22
22
  ==== Repository
23
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.
24
+ To use the repository version, you need git ( http://git.or.cz ).
25
+ Chances are, there is a git package for your Linux/UNIX flavour.
26
26
 
27
27
  cd /where/you/want/m4dbi
28
- svn co http://rome.purepistos.net/svn/m4dbi
28
+ git clone git://github.com/Pistos/m4dbi.git
29
29
 
30
30
  Change the following to whatever the equivalent paths are for your system:
31
31
 
32
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
33
+ ln -s /path/to/cloned/m4dbi/lib/m4dbi
34
+ ln -s /path/to/cloned/m4dbi/lib/m4dbi.rb
35
35
 
36
36
  === Usage
37
37
 
@@ -40,7 +40,7 @@ generated from the spec files under the spec/ dir.
40
40
 
41
41
  === Source Code
42
42
 
43
- Browse source at http://rome.purepistos.net/issues/m4dbi/browser/trunk .
43
+ Browse source at http://github.com/Pistos/m4dbi/tree/master .
44
44
  See coverage at http://rome.purepistos.net/m4dbi/rcov .
45
45
  Very limited rdocs at http://rome.purepistos.net/m4dbi/rdoc .
46
46
 
data/LICENCE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT Licence
2
+
3
+ Copyright (c) 2008-2009 Pistos
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/lib/m4dbi.rb CHANGED
@@ -1,8 +1,14 @@
1
1
  require 'rubygems'
2
2
 
3
- require 'm4dbi/traits'
4
- require 'm4dbi/hash'
5
- require 'm4dbi/database-handle'
6
- require 'm4dbi/row'
7
- require 'm4dbi/model'
8
- require 'm4dbi/collection'
3
+ M4DBI_VERSION = '0.6.0'
4
+
5
+ __DIR__ = File.expand_path( File.dirname( __FILE__ ) )
6
+
7
+ require "#{__DIR__}/m4dbi/traits"
8
+ require "#{__DIR__}/m4dbi/hash"
9
+ require "#{__DIR__}/m4dbi/array"
10
+ require "#{__DIR__}/m4dbi/database-handle"
11
+ require "#{__DIR__}/m4dbi/row"
12
+ require "#{__DIR__}/m4dbi/timestamp"
13
+ require "#{__DIR__}/m4dbi/model"
14
+ require "#{__DIR__}/m4dbi/collection"
@@ -0,0 +1,5 @@
1
+ class Array
2
+ def to_placeholders
3
+ map { '?' }.join( ', ' )
4
+ end
5
+ end
@@ -20,17 +20,40 @@ module DBI
20
20
  end
21
21
  end; end
22
22
 
23
+ module DBD; module Mysql
24
+ module DatabaseNameAccessor
25
+ def dbname
26
+ select_column( "SELECT DATABASE()" )
27
+ end
28
+ end
29
+ end; end
30
+
31
+ module DBD; module SQLite3
32
+ module DatabaseNameAccessor
33
+ def dbname
34
+ select_one( "PRAGMA database_list" )[ 2 ]
35
+ end
36
+ end
37
+ end; end
38
+
23
39
  class DatabaseHandle
40
+ attr_reader :transactions
41
+
24
42
  alias old_initialize initialize
25
- def initialize( handle )
43
+ def initialize( *args )
26
44
  DBI::DatabaseHandle.last_handle = self
27
- handle = old_initialize( handle )
45
+ handle = old_initialize( *args )
28
46
  @mutex = Mutex.new
47
+ @transactions = Array.new
29
48
 
30
49
  # Hackery to expose dbname.
31
50
  if defined?( DBI::DBD::Pg::Database ) and ( DBI::DBD::Pg::Database === @handle )
32
51
  @handle.extend DBI::DBD::Pg::ConnectionDatabaseNameAccessor
33
52
  extend DBI::DBD::Pg::DatabaseNameAccessor
53
+ elsif defined?( DBI::DBD::Mysql::Database ) and ( DBI::DBD::Mysql::Database === @handle )
54
+ extend DBI::DBD::Mysql::DatabaseNameAccessor
55
+ elsif defined?( DBI::DBD::SQLite3::Database ) and ( DBI::DBD::SQLite3::Database === @handle )
56
+ extend DBI::DBD::SQLite3::DatabaseNameAccessor
34
57
  end
35
58
  # TODO: more DBDs
36
59
 
@@ -44,12 +67,18 @@ module DBI
44
67
  # database handle.
45
68
  def one_transaction
46
69
  @mutex.synchronize do
70
+ # Keep track of transactions for debugging purposes
71
+ transaction = { :time => ::Time.now, :stack => caller }
72
+ @transactions << transaction
73
+
47
74
  auto_commit = self[ 'AutoCommit' ]
48
75
  self[ 'AutoCommit' ] = false
49
76
  result = transaction do
50
77
  yield self
51
78
  end
52
79
  self[ 'AutoCommit' ] = auto_commit
80
+
81
+ @transactions.delete transaction
53
82
  result
54
83
  end
55
84
  end
@@ -58,6 +87,8 @@ module DBI
58
87
  row = select_one( statement, *bindvars )
59
88
  if row
60
89
  row[ 0 ]
90
+ else
91
+ raise DBI::DataError.new( "Query returned no rows." )
61
92
  end
62
93
  end
63
94
 
data/lib/m4dbi/hash.rb CHANGED
@@ -1,21 +1,49 @@
1
1
  class Hash
2
- def to_clause( join_string )
2
+ COMPACT_NILS = true
3
+ DONT_COMPACT_NILS = false
4
+
5
+ # Takes an optional block to provide a single "field = ?" type subclause
6
+ # for each key-value pair.
7
+ def to_clause( join_string, compact_nils = DONT_COMPACT_NILS )
3
8
  # The clause items and the values have to be in the same order.
4
9
  keys_ = keys
5
- clause = keys_.map { |field|
6
- "#{field} = ?"
7
- }.join( join_string )
10
+ if block_given?
11
+ mapping = keys_.map { |field| yield field }
12
+ else
13
+ mapping = keys_.map { |field| "#{field} = ?" }
14
+ end
15
+ clause = mapping.join( join_string )
8
16
  values_ = keys_.map { |key|
9
17
  self[ key ]
10
18
  }
19
+ if compact_nils
20
+ values_.compact!
21
+ end
11
22
  [ clause, values_ ]
12
23
  end
13
24
 
14
25
  def to_where_clause
15
- to_clause( " AND " )
26
+ to_clause( " AND ", COMPACT_NILS ) { |field|
27
+ if self[ field ].nil?
28
+ "#{field} IS NULL"
29
+ else
30
+ "#{field} = ?"
31
+ end
32
+ }
16
33
  end
17
34
 
18
35
  def to_set_clause
19
36
  to_clause( ", " )
20
37
  end
38
+
39
+ if method_defined? :slice
40
+ warn "Hash#slice already defined; redefining."
41
+ end
42
+ def slice( *desired_keys )
43
+ Hash[
44
+ select { |key,value|
45
+ desired_keys.include? key
46
+ }
47
+ ]
48
+ end
21
49
  end
data/lib/m4dbi/model.rb CHANGED
@@ -11,26 +11,39 @@ module DBI
11
11
 
12
12
  extend Enumerable
13
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
- )
14
+ def self.[]( first_arg, *args )
15
+ if args.size == 0
16
+ case first_arg
17
+ when Hash
18
+ clause, values = first_arg.to_where_clause
19
+ when NilClass
20
+ clause = pk_clause
21
+ values = [ first_arg ]
22
+ else # single value
23
+ clause = pk_clause
24
+ values = Array( first_arg )
25
+ end
26
+ else
27
+ clause = pk_clause
28
+ values = [ first_arg ] + args
27
29
  end
28
30
 
31
+ row = dbh.select_one(
32
+ "SELECT * FROM #{table} WHERE #{clause}",
33
+ *values
34
+ )
35
+
29
36
  if row
30
37
  self.new( row )
31
38
  end
32
39
  end
33
40
 
41
+ def self.pk_clause
42
+ pk.map { |col|
43
+ "#{col} = ?"
44
+ }.join( ' AND ' )
45
+ end
46
+
34
47
  def self.from_rows( rows )
35
48
  rows.map { |r| self.new( r ) }
36
49
  end
@@ -85,7 +98,7 @@ module DBI
85
98
  end
86
99
 
87
100
  def self.count
88
- dbh.select_column( "SELECT COUNT(*) FROM #{table}" )
101
+ dbh.select_column( "SELECT COUNT(*) FROM #{table}" ).to_i
89
102
  end
90
103
 
91
104
  def self.create( hash = {} )
@@ -115,9 +128,16 @@ module DBI
115
128
  *values
116
129
  )
117
130
  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 )
131
+ pk_hash = hash.slice( *(
132
+ self.pk.map { |pk_col| pk_col.to_sym }
133
+ ) )
134
+ if pk_hash.empty?
135
+ pk_hash = hash.slice( *(
136
+ self.pk.map { |pk_col| pk_col.to_s }
137
+ ) )
138
+ end
139
+ if not pk_hash.empty?
140
+ rec = self.one_where( pk_hash )
121
141
  else
122
142
  begin
123
143
  rec = last_record( dbh_ )
@@ -189,11 +209,12 @@ module DBI
189
209
  )
190
210
  end
191
211
 
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 ]
212
+ def self.update_one( *args )
213
+ set_clause, set_params = args[ -1 ].to_set_clause
214
+ pk_values = args[ 0..-2 ]
215
+ params = set_params + pk_values
195
216
  dbh.do(
196
- "UPDATE #{table} SET #{set_clause} WHERE #{pk} = ?",
217
+ "UPDATE #{table} SET #{set_clause} WHERE #{pk_clause}",
197
218
  *params
198
219
  )
199
220
  end
@@ -233,6 +254,7 @@ module DBI
233
254
  j.#{m1_fk} = ?
234
255
  AND m2.id = j.#{m2_fk}
235
256
  },
257
+ # TODO: m2.id? Should be m2.pk or something
236
258
  pk
237
259
  )
238
260
  end
@@ -249,6 +271,7 @@ module DBI
249
271
  j.#{m2_fk} = ?
250
272
  AND m1.id = j.#{m1_fk}
251
273
  },
274
+ # TODO: Should be m1.pk not m1.id
252
275
  pk
253
276
  )
254
277
  end
@@ -267,17 +290,44 @@ module DBI
267
290
  end
268
291
 
269
292
  def method_missing( method, *args )
270
- @row.send( method, *args )
293
+ begin
294
+ @row.send( method, *args )
295
+ rescue NoMethodError => e
296
+ raise NoMethodError.new(
297
+ "undefined method '#{method}' for #{self}",
298
+ method,
299
+ args
300
+ )
301
+ end
271
302
  end
272
303
 
304
+ # Returns a single value for single-column primary keys,
305
+ # returns an Array for multi-column primary keys.
273
306
  def pk
274
- @row[ self.class.pk ]
307
+ if pk_columns.size == 1
308
+ @row[ pk_columns[ 0 ] ]
309
+ else
310
+ pk_values
311
+ end
275
312
  end
276
313
 
277
- def pk_column
314
+ # Always returns an Array of values, even for single-column primary keys.
315
+ def pk_values
316
+ pk_columns.map { |col|
317
+ @row[ col ]
318
+ }
319
+ end
320
+
321
+ def pk_columns
278
322
  self.class.pk
279
323
  end
280
324
 
325
+ def pk_clause
326
+ pk_columns.map { |col|
327
+ "#{col} = ?"
328
+ }.join( ' AND ' )
329
+ end
330
+
281
331
  def ==( other )
282
332
  other and ( pk == other.pk )
283
333
  end
@@ -293,17 +343,23 @@ module DBI
293
343
  def set( hash )
294
344
  set_clause, set_params = hash.to_set_clause
295
345
  set_params << pk
296
- dbh.do(
297
- "UPDATE #{table} SET #{set_clause} WHERE #{pk_column} = ?",
346
+ num_updated = dbh.do(
347
+ "UPDATE #{table} SET #{set_clause} WHERE #{pk_clause}",
298
348
  *set_params
299
349
  )
350
+ if num_updated > 0
351
+ hash.each do |key,value|
352
+ @row[ key ] = value
353
+ end
354
+ end
355
+ num_updated
300
356
  end
301
357
 
302
358
  # Returns true iff the record and only the record was successfully deleted.
303
359
  def delete
304
360
  num_deleted = dbh.do(
305
- "DELETE FROM #{table} WHERE #{pk_column} = ?",
306
- pk
361
+ "DELETE FROM #{table} WHERE #{pk_clause}",
362
+ *pk_values
307
363
  )
308
364
  num_deleted == 1
309
365
  end
@@ -316,18 +372,21 @@ module DBI
316
372
 
317
373
  # Define a new DBI::Model like this:
318
374
  # 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' )
375
+ # You can specify the primary key column(s) using an array, like so:
376
+ # class Author < DBI::Model( :authors, [ 'auth_num' ] ); end
377
+ def self.Model( table, pk_ = [ 'id' ] )
322
378
  h = DBI::DatabaseHandle.last_handle
323
379
  if h.nil? or not h.connected?
324
380
  raise DBI::Error.new( "Attempted to create a Model class without first connecting to a database." )
325
381
  end
382
+ if not pk_.respond_to? :each
383
+ raise DBI::Error.new( "Primary key must be enumerable (was given #{pk_.inspect})" )
384
+ end
326
385
 
327
386
  model_key =
328
- if defined?( DBI::DBD::Pg::Database ) and DBI::DBD::Pg::Database === h.handle
387
+ # DBD-dependent. Not all DBDs have dbname implemented by M4DBI.
388
+ if h.respond_to? :dbname
329
389
  "#{h.dbname}::#{table}"
330
- # TODO: more DBDs
331
390
  else
332
391
  table
333
392
  end
@@ -342,24 +401,35 @@ module DBI
342
401
  } )
343
402
 
344
403
  if defined?( DBI::DBD::Pg::Database ) and DBI::DBD::Pg::Database === h.handle
404
+ # TODO: This is broken for non-SERIAL or multi-column primary keys
345
405
  meta_def( "last_record".to_sym ) do |dbh_|
346
406
  self.s1 "SELECT * FROM #{table} WHERE #{pk} = currval( '#{table}_#{pk}_seq' );"
347
407
  end
408
+ elsif defined?( DBI::DBD::Mysql::Database ) and DBI::DBD::Mysql::Database === h.handle
409
+ meta_def( "last_record".to_sym ) do |dbh_|
410
+ self.s1 "SELECT * FROM #{table} WHERE #{pk} = LAST_INSERT_ID();"
411
+ end
412
+ elsif defined?( DBI::DBD::SQLite3::Database ) and DBI::DBD::SQLite3::Database === h.handle
413
+ meta_def( "last_record".to_sym ) do |dbh_|
414
+ self.s1 "SELECT * FROM #{table} WHERE #{pk} = last_insert_rowid();"
415
+ end
348
416
  # TODO: more DBDs
349
417
  end
350
418
 
351
419
  klass.trait[ :columns ].each do |col|
352
420
  colname = col[ 'name' ]
353
421
 
422
+ # Column readers
354
423
  class_def( colname.to_sym ) do
355
424
  @row[ colname ]
356
425
  end
357
426
 
427
+ # Column writers
358
428
  class_def( "#{colname}=".to_sym ) do |new_value|
359
429
  num_changed = dbh.do(
360
- "UPDATE #{table} SET #{colname} = ? WHERE #{pk_column} = ?",
430
+ "UPDATE #{table} SET #{colname} = ? WHERE #{pk_clause}",
361
431
  new_value,
362
- pk
432
+ *pk_values
363
433
  )
364
434
  if num_changed > 0
365
435
  @row[ colname ] = new_value
@@ -0,0 +1,20 @@
1
+ module DBI
2
+ class Timestamp
3
+ def method_missing( method, *args )
4
+ t = to_time
5
+ begin
6
+ t.send( method, *args )
7
+ rescue NoMethodError => e
8
+ raise NoMethodError.new(
9
+ "undefined method '#{method}' for #{self}",
10
+ method,
11
+ args
12
+ )
13
+ end
14
+ end
15
+
16
+ def <=>( other )
17
+ to_time <=> other.to_time
18
+ end
19
+ end
20
+ end
data/spec/dbi.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  require 'spec/helper'
2
2
 
3
- $dbh = DBI.connect( "DBI:Pg:m4dbi", "m4dbi", "m4dbi" )
4
- # See test-schema.sql and test-data.sql
3
+ $dbh = connect_to_spec_database
4
+ reset_data
5
5
 
6
6
  describe 'DBI::DatabaseHandle#select_column' do
7
7
 
@@ -11,6 +11,15 @@ describe 'DBI::DatabaseHandle#select_column' do
11
11
  )
12
12
  name.class.should.not.equal Array
13
13
  name.should.equal 'author1'
14
+
15
+ null = $dbh.select_column(
16
+ "SELECT c4 FROM many_col_table WHERE c3 = 40"
17
+ )
18
+ null.should.be.nil
19
+
20
+ should.raise( DBI::DataError ) do
21
+ $dbh.select_column( "SELECT name FROM authors WHERE 1+1 = 3" )
22
+ end
14
23
  end
15
24
 
16
25
  it 'selects one column of first row' do
@@ -26,7 +35,6 @@ describe 'DBI::DatabaseHandle#select_column' do
26
35
  )
27
36
  name.should.equal 'author3'
28
37
  end
29
-
30
38
  end
31
39
 
32
40
  describe 'DBI::DatabaseHandle#one_transaction' do
@@ -87,6 +95,8 @@ describe 'DBI::DatabaseHandle#one_transaction' do
87
95
 
88
96
  value = $dbh.sc "SELECT c1 FROM many_col_table WHERE id = 1;"
89
97
  value.should.equal( 11 + 1 + 1 )
98
+
99
+ reset_data
90
100
  end
91
101
 
92
102
  end
data/spec/hash.rb CHANGED
@@ -5,23 +5,43 @@ describe 'Hash' do
5
5
  h = {
6
6
  :a => 2,
7
7
  :b => 'foo',
8
+ :the_nil => nil,
8
9
  :abc => Time.now,
9
10
  :xyz => 9.02,
10
11
  }
11
12
  clause, values = h.to_clause( " AND " )
12
13
  where_clause, where_values = h.to_where_clause
13
14
 
14
- s = "a = ? AND b = ? AND abc = ? AND xyz = ?"
15
- clause.length.should.equal s.length
16
- where_clause.length.should.equal s.length
15
+ str = "a = ? AND b = ? AND the_nil = ? AND abc = ? AND xyz = ?"
16
+ where_str = "a = ? AND b = ? AND the_nil IS NULL AND abc = ? AND xyz = ?"
17
+ clause.length.should.equal str.length
18
+ where_clause.length.should.equal where_str.length
17
19
 
18
- h.each do |key,value|
20
+ h.each_key do |key|
19
21
  clause.should.match /#{key} = ?/
20
- where_clause.should.match /#{key} = ?/
21
- values.find { |v| v == value }.should.not.be.nil
22
- where_values.find { |v| v == value }.should.not.be.nil
22
+ if h[ key ].nil?
23
+ where_clause.should.match /#{key} IS NULL/
24
+ else
25
+ where_clause.should.match /#{key} = ?/
26
+ end
23
27
  end
24
- values.size.should.equal h.keys.size
25
- where_values.size.should.equal h.keys.size
28
+
29
+ values.should.equal h.values
30
+ where_values.should.equal h.values.compact
31
+ end
32
+
33
+ it 'offers a means to get subhashes via #slice' do
34
+ h = { :a => 1, :b => 2, :c => 3, :d => 4, :e => 5, :f => 6 }
35
+ h.slice( :b, :c ).should.equal(
36
+ { :b => 2, :c => 3 }
37
+ )
38
+ h.slice( :a, :b, :f ).should.equal(
39
+ { :a => 1, :b => 2, :f => 6 }
40
+ )
41
+ h.slice( :c, :e, :q ).should.equal(
42
+ { :c => 3, :e => 5 }
43
+ )
44
+ h.slice.should.equal Hash.new
45
+ h.slice( :g, :h ).should.equal Hash.new
26
46
  end
27
47
  end
data/spec/helper.rb CHANGED
@@ -11,3 +11,22 @@ $LOAD_PATH.unshift(
11
11
  )
12
12
 
13
13
  require 'm4dbi'
14
+
15
+ puts "DBI version: #{DBI::VERSION}"
16
+ puts "M4DBI version: #{M4DBI_VERSION}"
17
+
18
+ # See test-schema*.sql and test-data.sql
19
+ def connect_to_spec_database( database = ( ENV[ 'M4DBI_DATABASE' ] || 'm4dbi' ) )
20
+ driver = ENV[ 'M4DBI_DRIVER' ] || "DBI:Pg"
21
+ puts "Using DBI driver: '#{driver}'"
22
+ DBI.connect( "#{driver}:#{database}", "m4dbi", "m4dbi" )
23
+ end
24
+
25
+ def reset_data( dbh = $dbh, datafile = "test-data.sql" )
26
+ dir = File.dirname( __FILE__ )
27
+ File.read( "#{dir}/#{datafile}" ).split( /;/ ).each do |command|
28
+ if not command.strip.empty?
29
+ dbh.do command
30
+ end
31
+ end
32
+ end
data/spec/model.rb CHANGED
@@ -1,14 +1,5 @@
1
1
  require 'spec/helper'
2
2
 
3
- # See test-schema.sql and test-data.sql
4
-
5
- def reset_data( dbh = $dbh, datafile = "test-data.sql" )
6
- dir = File.dirname( __FILE__ )
7
- File.read( "#{dir}/#{datafile}" ).split( /;/ ).each do |command|
8
- dbh.do( command )
9
- end
10
- end
11
-
12
3
  describe 'DBI::Model' do
13
4
  it 'raises an exception when trying to define a model before connecting to a database' do
14
5
  dbh = DBI::DatabaseHandle.last_handle
@@ -21,7 +12,7 @@ describe 'DBI::Model' do
21
12
  end
22
13
  end
23
14
 
24
- $dbh = DBI.connect( "DBI:Pg:m4dbi", "m4dbi", "m4dbi" )
15
+ $dbh = connect_to_spec_database
25
16
  reset_data
26
17
 
27
18
  class ManyCol < DBI::Model( :many_col_table )
@@ -43,6 +34,7 @@ describe 'A DBI::Model subclass' do
43
34
  @m_post = Class.new( DBI::Model( :posts ) )
44
35
  @m_empty = Class.new( DBI::Model( :empty_table ) )
45
36
  @m_mc = Class.new( DBI::Model( :many_col_table ) )
37
+ @m_mcpk = Class.new( DBI::Model( :mcpk, [ :kc1, :kc2 ] ) )
46
38
  class Author < DBI::Model( :authors ); end
47
39
  end
48
40
 
@@ -78,7 +70,7 @@ describe 'A DBI::Model subclass' do
78
70
 
79
71
  class Author < DBI::Model( :authors ); end
80
72
 
81
- dbh = DBI.connect( "DBI:Pg:m4dbi", "m4dbi", "m4dbi" )
73
+ dbh = connect_to_spec_database
82
74
  new_handle = DBI::DatabaseHandle.last_handle
83
75
  new_handle.should.equal dbh
84
76
  new_handle.should.not.equal original_handle
@@ -93,7 +85,7 @@ describe 'A DBI::Model subclass' do
93
85
  a1.should.not.be.nil
94
86
  a1.name.should.equal 'author1'
95
87
 
96
- dbh = DBI.connect( "DBI:Pg:m4dbi2", "m4dbi", "m4dbi" )
88
+ dbh = connect_to_spec_database( 'm4dbi2' )
97
89
  reset_data( dbh, "test-data2.sql" )
98
90
 
99
91
  @m_author2 = Class.new( DBI::Model( :authors ) )
@@ -109,7 +101,7 @@ describe 'A DBI::Model subclass' do
109
101
  ensure
110
102
  # Clean up handles for later specs
111
103
  dbh.disconnect if dbh and dbh.connected?
112
- DBI.connect( "DBI:Pg:m4dbi", "m4dbi", "m4dbi" )
104
+ connect_to_spec_database
113
105
  end
114
106
  end
115
107
 
@@ -135,6 +127,26 @@ describe 'A DBI::Model subclass' do
135
127
  o.should.not.be.nil
136
128
  o.class.should.equal @m_author
137
129
  o.name.should.equal 'author2'
130
+
131
+ o = @m_mcpk[ [ 2, 2 ] ]
132
+ o.should.not.be.nil
133
+ o.class.should.equal @m_mcpk
134
+ o.val.should.equal 'two two'
135
+
136
+ o = @m_mcpk[ 1, 1 ]
137
+ o.should.not.be.nil
138
+ o.class.should.equal @m_mcpk
139
+ o.val.should.equal 'one one'
140
+
141
+ o = @m_mcpk[ { :kc1 => 5, :kc2 => 6 } ]
142
+ o.should.not.be.nil
143
+ o.class.should.equal @m_mcpk
144
+ o.val.should.equal 'five six'
145
+
146
+ should.not.raise( DBI::Error ) do
147
+ o = @m_author[ nil ]
148
+ o.should.be.nil
149
+ end
138
150
  end
139
151
 
140
152
  it 'provides hash-like single-record access via #[ field_hash ]' do
@@ -147,6 +159,16 @@ describe 'A DBI::Model subclass' do
147
159
  o.should.not.be.nil
148
160
  o.class.should.equal @m_post
149
161
  o.text.should.equal 'First post.'
162
+
163
+ o = @m_mc[ :c1 => 100, :c2 => 50 ]
164
+ o.should.not.be.nil
165
+ o.class.should.equal @m_mc
166
+ o.c3.should.equal 20
167
+
168
+ o = @m_mc[ :c1 => 100, :c2 => nil ]
169
+ o.should.not.be.nil
170
+ o.class.should.equal @m_mc
171
+ o.c3.should.equal 40
150
172
  end
151
173
 
152
174
  it 'returns nil from #[] when no record is found' do
@@ -158,9 +180,7 @@ describe 'A DBI::Model subclass' do
158
180
  end
159
181
 
160
182
  it 'provides multi-record access via #where( Hash )' do
161
- posts = @m_post.where(
162
- :author_id => 1
163
- )
183
+ posts = @m_post.where( :author_id => 1 )
164
184
  posts.should.not.be.nil
165
185
  posts.should.not.be.empty
166
186
  posts.size.should.equal 2
@@ -171,6 +191,24 @@ describe 'A DBI::Model subclass' do
171
191
  }
172
192
  p = sorted_posts.first
173
193
  p.text.should.equal 'First post.'
194
+
195
+ rows = @m_mc.where( :c1 => 100, :c2 => 50 )
196
+ rows.should.not.be.nil
197
+ rows.should.not.be.empty
198
+ rows.size.should.equal 1
199
+ row = rows[ 0 ]
200
+ row.class.should.equal @m_mc
201
+ row.c1.should.equal 100
202
+ row.c3.should.equal 20
203
+
204
+ rows = @m_mc.where( :c1 => 100, :c2 => nil )
205
+ rows.should.not.be.nil
206
+ rows.should.not.be.empty
207
+ rows.size.should.equal 1
208
+ row = rows[ 0 ]
209
+ row.class.should.equal @m_mc
210
+ row.c1.should.equal 100
211
+ row.c3.should.equal 40
174
212
  end
175
213
 
176
214
  it 'provides multi-record access via #where( String )' do
@@ -187,6 +225,20 @@ describe 'A DBI::Model subclass' do
187
225
  p.text.should.equal 'Second post.'
188
226
  end
189
227
 
228
+ it 'provides multi-record access via #where( String, param, param... )' do
229
+ posts = @m_post.where( "id < ?", 3 )
230
+ posts.should.not.be.nil
231
+ posts.should.not.be.empty
232
+ posts.size.should.equal 2
233
+ posts[ 0 ].class.should.equal @m_post
234
+
235
+ sorted_posts = posts.sort { |p1,p2|
236
+ p2._id <=> p1._id
237
+ }
238
+ p = sorted_posts.first
239
+ p.text.should.equal 'Second post.'
240
+ end
241
+
190
242
  it 'returns an empty array from #where when no records are found' do
191
243
  a = @m_author.where( :id => 999 )
192
244
  a.should.be.empty
@@ -200,6 +252,12 @@ describe 'A DBI::Model subclass' do
200
252
  post.should.not.be.nil
201
253
  post.class.should.equal @m_post
202
254
  post.text.should.equal 'Second post.'
255
+
256
+ row = @m_mc.one_where( :c1 => 100, :c2 => nil )
257
+ row.should.not.be.nil
258
+ row.class.should.equal @m_mc
259
+ row.c1.should.equal 100
260
+ row.c3.should.equal 40
203
261
  end
204
262
 
205
263
  it 'provides single-record access via #one_where( String )' do
@@ -209,6 +267,13 @@ describe 'A DBI::Model subclass' do
209
267
  post.id.should.equal 3
210
268
  end
211
269
 
270
+ it 'provides single-record access via #one_where( String, param, param... )' do
271
+ post = @m_post.one_where( "text LIKE ?", '%Third%' )
272
+ post.should.not.be.nil
273
+ post.class.should.equal @m_post
274
+ post.id.should.equal 3
275
+ end
276
+
212
277
  it 'returns nil from #one_where when no record is found' do
213
278
  a = @m_author.one_where( :id => 999 )
214
279
  a.should.be.nil
@@ -217,7 +282,6 @@ describe 'A DBI::Model subclass' do
217
282
  p.should.be.nil
218
283
  end
219
284
 
220
-
221
285
  it 'returns all table records via #all' do
222
286
  rows = @m_author.all
223
287
  rows.should.not.be.nil
@@ -225,8 +289,10 @@ describe 'A DBI::Model subclass' do
225
289
  rows.size.should.equal 3
226
290
 
227
291
  rows[ 0 ].id.should.equal 1
292
+ rows[ 0 ].class.should.equal @m_author
228
293
  rows[ 0 ].name.should.equal 'author1'
229
294
  rows[ 1 ].id.should.equal 2
295
+ rows[ 1 ].class.should.equal @m_author
230
296
  rows[ 1 ].name.should.equal 'author2'
231
297
  end
232
298
 
@@ -236,7 +302,7 @@ describe 'A DBI::Model subclass' do
236
302
  rows.should.be.empty
237
303
  end
238
304
 
239
- it 'returns any single record from #one' do
305
+ it 'returns a random single record from #one' do
240
306
  one = @m_author.one
241
307
  one.should.not.be.nil
242
308
  one.class.should.equal @m_author
@@ -324,6 +390,14 @@ describe 'A DBI::Model subclass' do
324
390
  a.should.not.respond_to :no_column_by_this_name
325
391
  a.name.should.equal 'author1'
326
392
  @m_author.count.should.equal n
393
+
394
+ n = @m_mc.count
395
+ row = @m_mc.find_or_create( :c1 => 100, :c2 => nil )
396
+ row.should.not.be.nil
397
+ row.class.should.equal @m_mc
398
+ row.c1.should.equal 100
399
+ row.c3.should.equal 40
400
+ @m_mc.count.should.equal n
327
401
  end
328
402
 
329
403
  it 'creates a record via #find_or_create( Hash )' do
@@ -373,7 +447,7 @@ describe 'A DBI::Model subclass' do
373
447
  posts[ 1 ].text.should.equal 'Third post.'
374
448
  posts[ 1 ].class.should.equal @m_post
375
449
 
376
- no_posts = @m_post.s( "SELECT * FROM posts WHERE FALSE" )
450
+ no_posts = @m_post.s( "SELECT * FROM posts WHERE 1+1 = 3" )
377
451
  no_posts.should.not.be.nil
378
452
  no_posts.should.be.empty
379
453
  end
@@ -402,7 +476,7 @@ describe 'A DBI::Model subclass' do
402
476
  post.author_id.should.equal 1
403
477
  post.text.should.equal 'Third post.'
404
478
 
405
- no_post = @m_post.s1( "SELECT * FROM posts WHERE FALSE" )
479
+ no_post = @m_post.s1( "SELECT * FROM posts WHERE 1+1 = 3" )
406
480
  no_post.should.be.nil
407
481
  end
408
482
 
@@ -426,16 +500,26 @@ describe 'A DBI::Model subclass' do
426
500
  end
427
501
 
428
502
  it 'provides a means to update records referred to by primary key value' do
429
- new_text = 'This is some new text.'
503
+ new_text = 'Some new text.'
430
504
 
431
505
  p2 = @m_post[ 2 ]
432
506
  p2.text.should.not.equal new_text
433
-
434
507
  @m_post.update_one( 2, { :text => new_text } )
435
-
436
508
  p2_ = @m_post[ 2 ]
437
509
  p2_.text.should.equal new_text
438
510
 
511
+ row = @m_mcpk[ 1, 1 ]
512
+ row.val.should.not.equal new_text
513
+ @m_mcpk.update_one( 1, 1, { :val => new_text } )
514
+ row = @m_mcpk[ 1, 1 ]
515
+ row.val.should.equal new_text
516
+
517
+ row = @m_mcpk[ 3, 4 ]
518
+ row.val.should.not.equal new_text
519
+ @m_mcpk.update_one( 3, 4, { :val => new_text } )
520
+ row = @m_mcpk[ 3, 4 ]
521
+ row.val.should.equal new_text
522
+
439
523
  reset_data
440
524
  end
441
525
 
@@ -560,6 +644,8 @@ describe 'A found DBI::Model subclass instance' do
560
644
  before do
561
645
  @m_author = Class.new( DBI::Model( :authors ) )
562
646
  @m_post = Class.new( DBI::Model( :posts ) )
647
+ @m_mc = Class.new( DBI::Model( :many_col_table ) )
648
+ @m_mcpk = Class.new( DBI::Model( :mcpk, [ :kc1, :kc2 ] ) )
563
649
  end
564
650
 
565
651
  it 'provides access to primary key value' do
@@ -568,6 +654,12 @@ describe 'A found DBI::Model subclass instance' do
568
654
 
569
655
  p = @m_post[ 3 ]
570
656
  p.pk.should.equal 3
657
+
658
+ r = @m_mcpk[ 1, 1 ]
659
+ r.pk.should.equal [ 1, 1 ]
660
+
661
+ r = @m_mcpk[ { :kc1 => 3, :kc2 => 4 } ]
662
+ r.pk.should.equal [ 3, 4 ]
571
663
  end
572
664
 
573
665
  it 'provides read access to fields via identically-named readers' do
@@ -595,6 +687,7 @@ describe 'A found DBI::Model subclass instance' do
595
687
 
596
688
  p3 = @m_post[ 3 ]
597
689
  p3.text = the_new_text
690
+ p3.text.should.equal the_new_text
598
691
 
599
692
  p3_ = @m_post[ 3 ]
600
693
  p3_.text.should.equal the_new_text
@@ -603,6 +696,16 @@ describe 'A found DBI::Model subclass instance' do
603
696
  p2_ = @m_post[ 2 ]
604
697
  p2_.text.should.equal p2.text
605
698
 
699
+ mc1 = @m_mc.create(
700
+ :id => 1,
701
+ :c1 => 2
702
+ )
703
+ mc1.c1.should.equal 2
704
+ mc1.c1 = nil
705
+ mc1.c1.should.be.nil
706
+ mc1_ = @m_mc[ 1 ]
707
+ mc1_.c1.should.be.nil
708
+
606
709
  reset_data
607
710
  end
608
711
 
@@ -620,11 +723,28 @@ describe 'A found DBI::Model subclass instance' do
620
723
  :author_id => 2,
621
724
  :text => the_new_text
622
725
  )
726
+ p.author_id.should.equal 2
727
+ p.text.should.equal the_new_text
623
728
 
624
729
  p_ = @m_post[ 1 ]
625
730
  p_.author_id.should.equal 2
626
731
  p_.text.should.equal the_new_text
627
732
 
733
+ mc1 = @m_mc.create(
734
+ :id => 1,
735
+ :c1 => 2,
736
+ :c2 => 3
737
+ )
738
+ mc1.set(
739
+ :c1 => nil,
740
+ :c2 => 4
741
+ )
742
+ mc1.c1.should.be.nil
743
+ mc1.c2.should.equal 4
744
+ mc1_ = @m_mc[ 1 ]
745
+ mc1_.c1.should.be.nil
746
+ mc1_.c2.should.equal 4
747
+
628
748
  reset_data
629
749
  end
630
750
 
@@ -645,18 +765,6 @@ describe 'A found DBI::Model subclass instance' do
645
765
  end
646
766
  end
647
767
 
648
- it 'allows a field to be incremented' do
649
- mc = ManyCol.create( :c1 => 50 )
650
- should.not.raise do
651
- mc.inc
652
- end
653
- end
654
- it 'allows a field to be decremented' do
655
- mc = ManyCol.create( :c1 => 50 )
656
- should.not.raise do
657
- mc.dec
658
- end
659
- end
660
768
  end
661
769
 
662
770
  describe 'DBI::Model (relationships)' do
@@ -740,7 +848,6 @@ describe 'DBI::Collection' do
740
848
  before do
741
849
  @m_author = Class.new( DBI::Model( :authors ) )
742
850
  @m_post = Class.new( DBI::Model( :posts ) )
743
- @m_fan = Class.new( DBI::Model( :fans ) )
744
851
 
745
852
  DBI::Model.one_to_many(
746
853
  @m_author, @m_post, :posts, :author, :author_id
metadata CHANGED
@@ -1,19 +1,30 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: m4dbi
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
5
- platform: ""
4
+ version: 0.6.0
5
+ platform: ruby
6
6
  authors:
7
7
  - Pistos
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-04-01 00:00:00 -04:00
12
+ date: 2008-11-10 00:00:00 -05:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: metaid
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: dbi
27
+ type: :runtime
17
28
  version_requirement:
18
29
  version_requirements: !ruby/object:Gem::Requirement
19
30
  requirements:
@@ -31,21 +42,25 @@ extra_rdoc_files:
31
42
  - HIM
32
43
  - READHIM
33
44
  - CHANGELOG
45
+ - LICENCE
34
46
  files:
35
47
  - HIM
36
48
  - READHIM
37
- - lib/m4dbi/database-handle.rb
49
+ - CHANGELOG
50
+ - LICENCE
51
+ - lib/m4dbi/array.rb
52
+ - lib/m4dbi/collection.rb
38
53
  - lib/m4dbi/row.rb
39
- - lib/m4dbi/model.rb
54
+ - lib/m4dbi/timestamp.rb
40
55
  - lib/m4dbi/traits.rb
41
- - lib/m4dbi/collection.rb
56
+ - lib/m4dbi/database-handle.rb
42
57
  - lib/m4dbi/hash.rb
58
+ - lib/m4dbi/model.rb
43
59
  - lib/m4dbi.rb
44
- - spec/helper.rb
45
- - spec/dbi.rb
46
60
  - spec/model.rb
61
+ - spec/dbi.rb
47
62
  - spec/hash.rb
48
- - CHANGELOG
63
+ - spec/helper.rb
49
64
  has_rdoc: false
50
65
  homepage: http://purepistos.net/m4dbi
51
66
  post_install_message:
@@ -66,15 +81,14 @@ required_rubygems_version: !ruby/object:Gem::Requirement
66
81
  version: "0"
67
82
  version:
68
83
  requirements:
69
- - dbi
70
84
  - bacon (optional)
71
- rubyforge_project:
72
- rubygems_version: 0.9.5
85
+ rubyforge_project: m4dbi
86
+ rubygems_version: 1.2.0
73
87
  signing_key:
74
88
  specification_version: 2
75
89
  summary: Models (and More) for DBI
76
90
  test_files:
77
- - spec/helper.rb
78
- - spec/dbi.rb
79
91
  - spec/model.rb
92
+ - spec/dbi.rb
80
93
  - spec/hash.rb
94
+ - spec/helper.rb