m4dbi 0.6.2 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- data/{READHIM → README} +0 -0
- data/lib/m4dbi.rb +16 -4
- data/lib/m4dbi/collection.rb +7 -8
- data/lib/m4dbi/database.rb +73 -0
- data/lib/m4dbi/error.rb +4 -0
- data/lib/m4dbi/model.rb +55 -41
- data/spec/database.rb +84 -0
- data/spec/hash.rb +5 -5
- data/spec/helper.rb +18 -6
- data/spec/model.rb +211 -96
- metadata +25 -35
- data/HIM +0 -50
- data/lib/m4dbi/database-handle.rb +0 -117
- data/lib/m4dbi/row.rb +0 -35
- data/lib/m4dbi/timestamp.rb +0 -20
- data/spec/dbi.rb +0 -142
metadata
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: m4dbi
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
4
|
+
prerelease:
|
5
|
+
version: 0.7.0
|
5
6
|
platform: ruby
|
6
7
|
authors:
|
7
8
|
- Pistos
|
@@ -9,58 +10,46 @@ autorequire:
|
|
9
10
|
bindir: bin
|
10
11
|
cert_chain: []
|
11
12
|
|
12
|
-
date:
|
13
|
+
date: 2011-08-14 00:00:00 -04:00
|
13
14
|
default_executable:
|
14
15
|
dependencies:
|
15
16
|
- !ruby/object:Gem::Dependency
|
16
17
|
name: metaid
|
17
|
-
|
18
|
-
|
19
|
-
|
18
|
+
prerelease: false
|
19
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
20
|
+
none: false
|
20
21
|
requirements:
|
21
22
|
- - ">="
|
22
23
|
- !ruby/object:Gem::Version
|
23
24
|
version: "0"
|
24
|
-
version:
|
25
|
-
- !ruby/object:Gem::Dependency
|
26
|
-
name: dbi
|
27
25
|
type: :runtime
|
28
|
-
|
29
|
-
|
30
|
-
requirements:
|
31
|
-
- - ">="
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: "0"
|
34
|
-
version:
|
35
|
-
description: M4DBI provides models, associations and some convenient extensions to Ruby DBI.
|
26
|
+
version_requirements: *id001
|
27
|
+
description: M4DBI provides models, associations and some convenient extensions to RDBI.
|
36
28
|
email: pistos at purepistos dot net
|
37
29
|
executables: []
|
38
30
|
|
39
31
|
extensions: []
|
40
32
|
|
41
33
|
extra_rdoc_files:
|
42
|
-
-
|
43
|
-
- READHIM
|
34
|
+
- README
|
44
35
|
- CHANGELOG
|
45
36
|
- LICENCE
|
46
37
|
files:
|
47
|
-
-
|
48
|
-
- READHIM
|
38
|
+
- README
|
49
39
|
- CHANGELOG
|
50
40
|
- LICENCE
|
51
|
-
- lib/m4dbi.rb
|
52
|
-
- lib/m4dbi/
|
53
|
-
- lib/m4dbi/array.rb
|
41
|
+
- lib/m4dbi/database.rb
|
42
|
+
- lib/m4dbi/model.rb
|
54
43
|
- lib/m4dbi/traits.rb
|
55
44
|
- lib/m4dbi/collection.rb
|
56
45
|
- lib/m4dbi/hash.rb
|
57
|
-
- lib/m4dbi/
|
58
|
-
- lib/m4dbi/
|
59
|
-
- lib/m4dbi
|
60
|
-
- spec/
|
61
|
-
- spec/hash.rb
|
46
|
+
- lib/m4dbi/array.rb
|
47
|
+
- lib/m4dbi/error.rb
|
48
|
+
- lib/m4dbi.rb
|
49
|
+
- spec/database.rb
|
62
50
|
- spec/model.rb
|
63
51
|
- spec/helper.rb
|
52
|
+
- spec/hash.rb
|
64
53
|
has_rdoc: true
|
65
54
|
homepage: http://purepistos.net/m4dbi
|
66
55
|
licenses: []
|
@@ -71,26 +60,27 @@ rdoc_options: []
|
|
71
60
|
require_paths:
|
72
61
|
- lib
|
73
62
|
required_ruby_version: !ruby/object:Gem::Requirement
|
63
|
+
none: false
|
74
64
|
requirements:
|
75
65
|
- - ">="
|
76
66
|
- !ruby/object:Gem::Version
|
77
67
|
version: "0"
|
78
|
-
version:
|
79
68
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
69
|
+
none: false
|
80
70
|
requirements:
|
81
71
|
- - ">="
|
82
72
|
- !ruby/object:Gem::Version
|
83
73
|
version: "0"
|
84
|
-
version:
|
85
74
|
requirements:
|
75
|
+
- rdbi
|
86
76
|
- bacon (optional)
|
87
|
-
rubyforge_project:
|
88
|
-
rubygems_version: 1.
|
77
|
+
rubyforge_project:
|
78
|
+
rubygems_version: 1.6.2
|
89
79
|
signing_key:
|
90
80
|
specification_version: 3
|
91
|
-
summary: Models (and More) for
|
81
|
+
summary: Models (and More) for RDBI
|
92
82
|
test_files:
|
93
|
-
- spec/
|
94
|
-
- spec/hash.rb
|
83
|
+
- spec/database.rb
|
95
84
|
- spec/model.rb
|
96
85
|
- spec/helper.rb
|
86
|
+
- spec/hash.rb
|
data/HIM
DELETED
@@ -1,50 +0,0 @@
|
|
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 git ( http://git.or.cz ).
|
25
|
-
Chances are, there is a git package for your Linux/UNIX flavour.
|
26
|
-
|
27
|
-
cd /where/you/want/m4dbi
|
28
|
-
git clone git://github.com/Pistos/m4dbi.git
|
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/cloned/m4dbi/lib/m4dbi
|
34
|
-
ln -s /path/to/cloned/m4dbi/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://github.com/Pistos/m4dbi/tree/master .
|
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.
|
@@ -1,117 +0,0 @@
|
|
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
|
-
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
|
-
|
39
|
-
class DatabaseHandle
|
40
|
-
attr_reader :transactions
|
41
|
-
|
42
|
-
alias old_initialize initialize
|
43
|
-
def initialize( *args )
|
44
|
-
DBI::DatabaseHandle.last_handle = self
|
45
|
-
handle = old_initialize( *args )
|
46
|
-
@mutex = Mutex.new
|
47
|
-
@transactions = Array.new
|
48
|
-
|
49
|
-
# Hackery to expose dbname.
|
50
|
-
if defined?( DBI::DBD::Pg::Database ) and ( DBI::DBD::Pg::Database === @handle )
|
51
|
-
@handle.extend DBI::DBD::Pg::ConnectionDatabaseNameAccessor
|
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
|
57
|
-
end
|
58
|
-
# TODO: more DBDs
|
59
|
-
|
60
|
-
handle
|
61
|
-
end
|
62
|
-
|
63
|
-
# Atomically disable autocommit, do transaction, and reenable.
|
64
|
-
# Used for a single transaction when autocommit is normally left on.
|
65
|
-
# Only one thread can execute one_transaction at a time,
|
66
|
-
# since we need to thread protect the AutoCommit property of the
|
67
|
-
# database handle.
|
68
|
-
def one_transaction
|
69
|
-
@mutex.synchronize do
|
70
|
-
# Keep track of transactions for debugging purposes
|
71
|
-
transaction = { :time => ::Time.now, :stack => caller }
|
72
|
-
@transactions << transaction
|
73
|
-
|
74
|
-
auto_commit = self[ 'AutoCommit' ]
|
75
|
-
self[ 'AutoCommit' ] = false
|
76
|
-
result = transaction do
|
77
|
-
yield self
|
78
|
-
end
|
79
|
-
self[ 'AutoCommit' ] = auto_commit
|
80
|
-
|
81
|
-
@transactions.delete transaction
|
82
|
-
result
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
|
-
def select_column( statement, *bindvars )
|
87
|
-
row = select_one( statement, *bindvars )
|
88
|
-
if row
|
89
|
-
row[ 0 ]
|
90
|
-
else
|
91
|
-
raise DBI::DataError.new( "Query returned no rows." )
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
|
-
alias s select_all
|
96
|
-
alias s1 select_one
|
97
|
-
alias sc select_column
|
98
|
-
alias u do
|
99
|
-
alias i do
|
100
|
-
alias d do
|
101
|
-
|
102
|
-
class << self
|
103
|
-
def last_handle
|
104
|
-
@handle# ||= create_handle
|
105
|
-
end
|
106
|
-
|
107
|
-
def last_handle=( handle )
|
108
|
-
@handle = handle
|
109
|
-
end
|
110
|
-
end
|
111
|
-
|
112
|
-
end
|
113
|
-
end
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
data/lib/m4dbi/row.rb
DELETED
@@ -1,35 +0,0 @@
|
|
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[ 0 ]
|
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
|
data/lib/m4dbi/timestamp.rb
DELETED
@@ -1,20 +0,0 @@
|
|
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
DELETED
@@ -1,142 +0,0 @@
|
|
1
|
-
require 'spec/helper'
|
2
|
-
|
3
|
-
$dbh = connect_to_spec_database
|
4
|
-
reset_data
|
5
|
-
|
6
|
-
describe 'DBI::DatabaseHandle#select_column' do
|
7
|
-
|
8
|
-
it 'selects one column' do
|
9
|
-
name = $dbh.select_column(
|
10
|
-
"SELECT name FROM authors LIMIT 1"
|
11
|
-
)
|
12
|
-
name.class.should.not.equal Array
|
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
|
23
|
-
end
|
24
|
-
|
25
|
-
it 'selects one column of first row' do
|
26
|
-
name = $dbh.select_column(
|
27
|
-
"SELECT name FROM authors ORDER BY name DESC"
|
28
|
-
)
|
29
|
-
name.should.equal 'author3'
|
30
|
-
end
|
31
|
-
|
32
|
-
it 'selects first column of first row' do
|
33
|
-
name = $dbh.select_column(
|
34
|
-
"SELECT name, id FROM authors ORDER BY name DESC"
|
35
|
-
)
|
36
|
-
name.should.equal 'author3'
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
describe 'DBI::DatabaseHandle#one_transaction' do
|
41
|
-
|
42
|
-
it 'turns off autocommit for the duration of a single transaction' do
|
43
|
-
$dbh.d( "DELETE FROM many_col_table;" )
|
44
|
-
$dbh.i( "INSERT INTO many_col_table ( id, c1 ) VALUES ( 1, 10 );" )
|
45
|
-
|
46
|
-
# Here we will attempt to increment a value two times in parallel.
|
47
|
-
# If each multi-operation transaction is truly atomic, we expect that
|
48
|
-
# the final value will reflect two increments.
|
49
|
-
# If atomicity is not respected, the value should only reflect one
|
50
|
-
# increment.
|
51
|
-
|
52
|
-
# First, we test the non-transactional case, to show failure.
|
53
|
-
|
54
|
-
thread1 = Thread.new do
|
55
|
-
value = $dbh.sc "SELECT c1 FROM many_col_table WHERE id = 1;"
|
56
|
-
value.should.equal 10
|
57
|
-
sleep 1 # seconds
|
58
|
-
$dbh.u "UPDATE many_col_table SET c1 = ?", ( value + 1 )
|
59
|
-
end
|
60
|
-
|
61
|
-
sleep 0.5
|
62
|
-
|
63
|
-
thread2 = Thread.new do
|
64
|
-
value = $dbh.sc "SELECT c1 FROM many_col_table WHERE id = 1;"
|
65
|
-
value.should.equal 10
|
66
|
-
# Update right away
|
67
|
-
$dbh.u "UPDATE many_col_table SET c1 = ?", ( value + 1 )
|
68
|
-
end
|
69
|
-
|
70
|
-
thread2.join
|
71
|
-
thread1.join
|
72
|
-
|
73
|
-
value = $dbh.sc "SELECT c1 FROM many_col_table WHERE id = 1;"
|
74
|
-
# Failure; two increments should give a final value of 12.
|
75
|
-
value.should.equal( 10 + 1 )
|
76
|
-
|
77
|
-
# Now, we show that transactions keep things sane.
|
78
|
-
|
79
|
-
thread1 = Thread.new do
|
80
|
-
$dbh.one_transaction do |dbh|
|
81
|
-
value = dbh.sc "SELECT c1 FROM many_col_table WHERE id = 1;"
|
82
|
-
sleep 1 # seconds
|
83
|
-
dbh.u "UPDATE many_col_table SET c1 = ?", ( value + 1 )
|
84
|
-
end
|
85
|
-
end
|
86
|
-
|
87
|
-
sleep 0.5
|
88
|
-
|
89
|
-
thread2 = Thread.new do
|
90
|
-
$dbh.one_transaction do |dbh|
|
91
|
-
value = dbh.sc "SELECT c1 FROM many_col_table WHERE id = 1;"
|
92
|
-
# Update right away
|
93
|
-
dbh.u "UPDATE many_col_table SET c1 = ?", ( value + 1 )
|
94
|
-
end
|
95
|
-
end
|
96
|
-
|
97
|
-
thread2.join
|
98
|
-
thread1.join
|
99
|
-
|
100
|
-
value = $dbh.sc "SELECT c1 FROM many_col_table WHERE id = 1;"
|
101
|
-
value.should.equal( 11 + 1 + 1 )
|
102
|
-
|
103
|
-
reset_data
|
104
|
-
end
|
105
|
-
|
106
|
-
end
|
107
|
-
|
108
|
-
describe 'DBI::Row accessors' do
|
109
|
-
|
110
|
-
it 'provide read access via #fieldname' do
|
111
|
-
row = $dbh.select_one(
|
112
|
-
"SELECT * FROM posts ORDER BY author_id DESC LIMIT 1"
|
113
|
-
)
|
114
|
-
row.should.not.equal nil
|
115
|
-
|
116
|
-
row._id.should.be.same_as row[ 'id' ]
|
117
|
-
row.id_.should.be.same_as row[ 'id' ]
|
118
|
-
row.author_id.should.be.same_as row[ 'author_id' ]
|
119
|
-
row.text.should.be.same_as row[ 'text' ]
|
120
|
-
|
121
|
-
row.text.should.equal 'Second post.'
|
122
|
-
end
|
123
|
-
|
124
|
-
it 'provide in-memory (non-syncing) write access via #fieldname=' do
|
125
|
-
row = $dbh.select_one(
|
126
|
-
"SELECT * FROM posts ORDER BY author_id DESC LIMIT 1"
|
127
|
-
)
|
128
|
-
row.should.not.equal nil
|
129
|
-
|
130
|
-
old_id = row._id
|
131
|
-
row.id = old_id + 1
|
132
|
-
row._id.should.not.equal old_id
|
133
|
-
row._id.should.equal( old_id + 1 )
|
134
|
-
|
135
|
-
old_text = row.text
|
136
|
-
new_text = 'This is the new post text.'
|
137
|
-
row.text = new_text
|
138
|
-
row.text.should.not.equal old_text
|
139
|
-
row.text.should.equal new_text
|
140
|
-
end
|
141
|
-
|
142
|
-
end
|