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 CHANGED
@@ -1,3 +1,8 @@
1
+ ## 0.6.1
2
+
3
+ - Fixed major Hash#slice bug that only surfaced in Ruby 1.8.6 and earier.
4
+ - Minor rdoc and spec additions or adjustments.
5
+
1
6
  ## 0.6.0
2
7
 
3
8
  - Compatibility with DBI 0.4.0 ensured.
@@ -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.pk} = ?
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
- row = DBI::Row.new(
107
- columns.collect { |c| c[ 'name' ] },
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 = row.to_h
112
- hash.to_a.each do |key,value|
113
- if value == M4DBI_UNASSIGNED
114
- hash.delete( key )
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 = nil
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 #{self}",
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 array, like so:
376
- # class Author < DBI::Model( :authors, [ 'auth_num' ] ); end
377
- def self.Model( table, pk_ = [ 'id' ] )
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 #{pk} = currval( '#{table}_#{pk}_seq' );"
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 #{pk} = LAST_INSERT_ID();"
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 #{pk} = last_insert_rowid();"
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 ] = *args
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
@@ -1,6 +1,6 @@
1
1
  require 'rubygems'
2
2
 
3
- M4DBI_VERSION = '0.6.1'
3
+ M4DBI_VERSION = '0.6.2'
4
4
 
5
5
  __DIR__ = File.expand_path( File.dirname( __FILE__ ) )
6
6
 
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 2 # seconds
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 2 # seconds
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
- dbh.disconnect if dbh and dbh.connected?
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.1
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-02-08 00:00:00 -05:00
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/array.rb
52
- - lib/m4dbi/collection.rb
51
+ - lib/m4dbi.rb
53
52
  - lib/m4dbi/row.rb
54
- - lib/m4dbi/timestamp.rb
53
+ - lib/m4dbi/array.rb
55
54
  - lib/m4dbi/traits.rb
56
- - lib/m4dbi/database-handle.rb
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: false
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.1
88
+ rubygems_version: 1.3.4
87
89
  signing_key:
88
- specification_version: 2
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