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.
@@ -0,0 +1,91 @@
1
+ # (taken from http://ramaze.net)
2
+
3
+ # Copyright (c) 2008 Michael Fellinger m.fellinger@gmail.com
4
+ # All files in this distribution are subject to the terms of the Ruby license.
5
+
6
+ Traits = Hash.new{|h,k| h[k] = {}} unless defined?(Traits)
7
+
8
+ # Extensions for Object
9
+
10
+ class Object
11
+
12
+ # Adds a method to Object to annotate your objects with certain traits.
13
+ # It's basically a simple Hash that takes the current object as key
14
+ #
15
+ # Example:
16
+ #
17
+ # class Foo
18
+ # trait :instance => false
19
+ #
20
+ # def initialize
21
+ # trait :instance => true
22
+ # end
23
+ # end
24
+ #
25
+ # Foo.trait[:instance]
26
+ # # false
27
+ #
28
+ # foo = Foo.new
29
+ # foo.trait[:instance]
30
+ # # true
31
+
32
+ def trait hash = nil
33
+ if hash
34
+ Traits[self].merge! hash
35
+ else
36
+ Traits[self]
37
+ end
38
+ end
39
+
40
+ # builds a trait from all the ancestors, closer ancestors
41
+ # overwrite distant ancestors
42
+ #
43
+ # class Foo
44
+ # trait :one => :eins
45
+ # trait :first => :erstes
46
+ # end
47
+ #
48
+ # class Bar < Foo
49
+ # trait :two => :zwei
50
+ # end
51
+ #
52
+ # class Foobar < Bar
53
+ # trait :three => :drei
54
+ # trait :first => :overwritten
55
+ # end
56
+ #
57
+ # Foobar.ancestral_trait
58
+ # {:three=>:drei, :two=>:zwei, :one=>:eins, :first=>:overwritten}
59
+
60
+ def ancestral_trait
61
+ if respond_to?(:ancestors)
62
+ ancs = ancestors
63
+ else
64
+ ancs = self.class.ancestors
65
+ end
66
+ ancs.reverse.inject({}){|s,v| s.merge(v.trait)}.merge(trait)
67
+ end
68
+
69
+ # trait for self.class
70
+
71
+ #def class_trait
72
+ #if respond_to?(:ancestors)
73
+ #trait
74
+ #else
75
+ #self.class.trait
76
+ #end
77
+ #end
78
+
79
+ def ancestral_trait_reader( *names )
80
+ names.each do |name|
81
+ name = name.to_sym
82
+ define_method( name ) { ancestral_trait[ name ] }
83
+ end
84
+ end
85
+ def ancestral_trait_class_reader( *names )
86
+ names.each do |name|
87
+ name = name.to_sym
88
+ meta_def( name ) { ancestral_trait[ name ] }
89
+ end
90
+ end
91
+ end
data/lib/m4dbi.rb ADDED
@@ -0,0 +1,8 @@
1
+ require 'rubygems'
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'
data/spec/dbi.rb ADDED
@@ -0,0 +1,128 @@
1
+ require 'spec/helper'
2
+
3
+ $dbh = DBI.connect( "DBI:Pg:m4dbi", "m4dbi", "m4dbi" )
4
+ # See test-schema.sql and test-data.sql
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
+ end
15
+
16
+ it 'selects one column of first row' do
17
+ name = $dbh.select_column(
18
+ "SELECT name FROM authors ORDER BY name DESC"
19
+ )
20
+ name.should.equal 'author3'
21
+ end
22
+
23
+ it 'selects first column of first row' do
24
+ name = $dbh.select_column(
25
+ "SELECT name, id FROM authors ORDER BY name DESC"
26
+ )
27
+ name.should.equal 'author3'
28
+ end
29
+
30
+ end
31
+
32
+ describe 'DBI::DatabaseHandle#one_transaction' do
33
+
34
+ it 'turns off autocommit for the duration of a single transaction' do
35
+ $dbh.d( "DELETE FROM many_col_table;" )
36
+ $dbh.i( "INSERT INTO many_col_table ( id, c1 ) VALUES ( 1, 10 );" )
37
+
38
+ # Here we will attempt to increment a value two times in parallel.
39
+ # If each multi-operation transaction is truly atomic, we expect that
40
+ # the final value will reflect two increments.
41
+ # If atomicity is not respected, the value should only reflect one
42
+ # increment.
43
+
44
+ # First, we test the non-transactional case, to show failure.
45
+
46
+ thread1 = Thread.new do
47
+ value = $dbh.sc "SELECT c1 FROM many_col_table WHERE id = 1;"
48
+ value.should.equal 10
49
+ sleep 2 # seconds
50
+ $dbh.u "UPDATE many_col_table SET c1 = ?", ( value + 1 )
51
+ end
52
+
53
+ thread2 = Thread.new do
54
+ value = $dbh.sc "SELECT c1 FROM many_col_table WHERE id = 1;"
55
+ value.should.equal 10
56
+ # Update right away
57
+ $dbh.u "UPDATE many_col_table SET c1 = ?", ( value + 1 )
58
+ end
59
+
60
+ thread2.join
61
+ thread1.join
62
+
63
+ value = $dbh.sc "SELECT c1 FROM many_col_table WHERE id = 1;"
64
+ # Failure; two increments should give a final value of 12.
65
+ value.should.equal( 10 + 1 )
66
+
67
+ # Now, we show that transactions keep things sane.
68
+
69
+ thread1 = Thread.new do
70
+ $dbh.one_transaction do |dbh|
71
+ value = dbh.sc "SELECT c1 FROM many_col_table WHERE id = 1;"
72
+ sleep 2 # seconds
73
+ dbh.u "UPDATE many_col_table SET c1 = ?", ( value + 1 )
74
+ end
75
+ end
76
+
77
+ thread2 = Thread.new do
78
+ $dbh.one_transaction do |dbh|
79
+ value = dbh.sc "SELECT c1 FROM many_col_table WHERE id = 1;"
80
+ # Update right away
81
+ dbh.u "UPDATE many_col_table SET c1 = ?", ( value + 1 )
82
+ end
83
+ end
84
+
85
+ thread2.join
86
+ thread1.join
87
+
88
+ value = $dbh.sc "SELECT c1 FROM many_col_table WHERE id = 1;"
89
+ value.should.equal( 11 + 1 + 1 )
90
+ end
91
+
92
+ end
93
+
94
+ describe 'DBI::Row accessors' do
95
+
96
+ it 'provide read access via #fieldname' do
97
+ row = $dbh.select_one(
98
+ "SELECT * FROM posts ORDER BY author_id DESC LIMIT 1"
99
+ )
100
+ row.should.not.equal nil
101
+
102
+ row._id.should.be.same_as row[ 'id' ]
103
+ row.id_.should.be.same_as row[ 'id' ]
104
+ row.author_id.should.be.same_as row[ 'author_id' ]
105
+ row.text.should.be.same_as row[ 'text' ]
106
+
107
+ row.text.should.equal 'Second post.'
108
+ end
109
+
110
+ it 'provide in-memory (non-syncing) write 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
+ old_id = row._id
117
+ row.id = old_id + 1
118
+ row._id.should.not.equal old_id
119
+ row._id.should.equal( old_id + 1 )
120
+
121
+ old_text = row.text
122
+ new_text = 'This is the new post text.'
123
+ row.text = new_text
124
+ row.text.should.not.equal old_text
125
+ row.text.should.equal new_text
126
+ end
127
+
128
+ end
data/spec/hash.rb ADDED
@@ -0,0 +1,27 @@
1
+ require 'spec/helper'
2
+
3
+ describe 'Hash' do
4
+ it 'is convertible to an SQL subclause and matching value Array' do
5
+ h = {
6
+ :a => 2,
7
+ :b => 'foo',
8
+ :abc => Time.now,
9
+ :xyz => 9.02,
10
+ }
11
+ clause, values = h.to_clause( " AND " )
12
+ where_clause, where_values = h.to_where_clause
13
+
14
+ s = "a = ? AND b = ? AND abc = ? AND xyz = ?"
15
+ clause.length.should.equal s.length
16
+ where_clause.length.should.equal s.length
17
+
18
+ h.each do |key,value|
19
+ 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
23
+ end
24
+ values.size.should.equal h.keys.size
25
+ where_values.size.should.equal h.keys.size
26
+ end
27
+ end
data/spec/helper.rb ADDED
@@ -0,0 +1,13 @@
1
+ require 'rubygems'
2
+ require 'bacon'
3
+
4
+ $LOAD_PATH.unshift(
5
+ File.expand_path(
6
+ File.join(
7
+ File.dirname( __FILE__ ),
8
+ "../lib"
9
+ )
10
+ )
11
+ )
12
+
13
+ require 'm4dbi'