momomoto 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/momomoto/base.rb +25 -13
- data/lib/momomoto/database.rb +9 -2
- data/lib/momomoto/datatype/bytea.rb +1 -1
- data/lib/momomoto/datatype/text.rb +1 -1
- data/lib/momomoto/row.rb +26 -0
- data/lib/momomoto/table.rb +45 -3
- data/test/test.sql +68 -0
- data/test/test_base.rb +3 -3
- data/test/test_functions.sql +25 -0
- data/test/test_information_schema.rb +1 -3
- data/test/test_table.rb +28 -6
- metadata +4 -2
data/lib/momomoto/base.rb
CHANGED
@@ -114,30 +114,43 @@ module Momomoto
|
|
114
114
|
# construct the Row class for the table
|
115
115
|
def initialize_row( row, table ) # :nodoc:
|
116
116
|
|
117
|
+
const_set( :Methods, Module.new ) if not const_defined?( :Methods )
|
118
|
+
const_set( :StandardMethods, Module.new ) if not const_defined?( :StandardMethods )
|
119
|
+
|
117
120
|
if not row.ancestors.member?( Momomoto::Row )
|
118
121
|
raise CriticalError, "Row is not inherited from Momomoto::Row"
|
119
122
|
end
|
120
123
|
|
121
124
|
row.instance_eval do class_variable_set( :@@table, table ) end
|
122
125
|
|
126
|
+
define_row_accessors( table )
|
127
|
+
|
128
|
+
row.instance_eval do
|
129
|
+
include table.const_get( :StandardMethods )
|
130
|
+
include table.const_get( :Methods )
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
134
|
+
|
135
|
+
# defines row setter and getter in the module StandardMethods which
|
136
|
+
# is later included in the Row class
|
137
|
+
def define_row_accessors( table ) #:nodoc:
|
138
|
+
method_module = const_get( :StandardMethods )
|
123
139
|
columns.each_with_index do | ( field_name, data_type ), index |
|
124
|
-
|
140
|
+
method_module.instance_eval do
|
125
141
|
# define getter for row class
|
126
|
-
if
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
instance_variable_get(:@data)[index]
|
134
|
-
end
|
142
|
+
if data_type.respond_to?( :filter_get )
|
143
|
+
define_method( field_name ) do
|
144
|
+
data_type.filter_get( instance_variable_get(:@data)[index] )
|
145
|
+
end
|
146
|
+
else
|
147
|
+
define_method( field_name ) do
|
148
|
+
instance_variable_get(:@data)[index]
|
135
149
|
end
|
136
150
|
end
|
137
151
|
|
138
152
|
# define setter for row class
|
139
|
-
|
140
|
-
define_method( setter_name ) do | value |
|
153
|
+
define_method( "#{field_name}=" ) do | value |
|
141
154
|
if not new_record? and table.primary_keys.member?( field_name )
|
142
155
|
raise Error, "Setting primary keys(#{field_name}) is only allowed for new records"
|
143
156
|
end
|
@@ -151,7 +164,6 @@ module Momomoto
|
|
151
164
|
|
152
165
|
end
|
153
166
|
end
|
154
|
-
|
155
167
|
end
|
156
168
|
|
157
169
|
end
|
data/lib/momomoto/database.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
|
2
2
|
begin
|
3
3
|
require 'rubygems'
|
4
|
-
|
4
|
+
gem 'ruby-postgres', '>= 0.7.1.2006.04.06'
|
5
|
+
require 'postgres'
|
5
6
|
rescue LoadError
|
6
7
|
require 'postgres'
|
7
8
|
end
|
@@ -63,7 +64,7 @@ module Momomoto
|
|
63
64
|
def disconnect
|
64
65
|
@connection.close
|
65
66
|
@connection = nil
|
66
|
-
@transaction_active =
|
67
|
+
@transaction_active = false
|
67
68
|
end
|
68
69
|
|
69
70
|
# execute a query
|
@@ -71,6 +72,12 @@ module Momomoto
|
|
71
72
|
puts sql if Momomoto.debug
|
72
73
|
@connection.query( sql )
|
73
74
|
rescue => e
|
75
|
+
if @connection.status == PGconn::CONNECTION_BAD
|
76
|
+
begin
|
77
|
+
connect
|
78
|
+
rescue
|
79
|
+
end
|
80
|
+
end
|
74
81
|
raise CriticalError, "#{e}: #{sql}"
|
75
82
|
end
|
76
83
|
|
data/lib/momomoto/row.rb
CHANGED
@@ -50,6 +50,7 @@ module Momomoto
|
|
50
50
|
self.class.table.delete( self )
|
51
51
|
end
|
52
52
|
|
53
|
+
# convert row to hash
|
53
54
|
def to_hash
|
54
55
|
hash = {}
|
55
56
|
self.class.table.columns.keys.each do | key |
|
@@ -58,6 +59,31 @@ module Momomoto
|
|
58
59
|
hash
|
59
60
|
end
|
60
61
|
|
62
|
+
# generic setter for column values
|
63
|
+
def set_column( column, value )
|
64
|
+
table = self.class.table
|
65
|
+
if not new_record? and table.primary_keys.member?( column.to_sym )
|
66
|
+
raise Error, "Setting primary keys(#{column}) is only allowed for new records"
|
67
|
+
end
|
68
|
+
value = table.columns[column.to_sym].filter_set( value )
|
69
|
+
index = table.column_order.index( column.to_sym )
|
70
|
+
if @data[index] != value
|
71
|
+
@dirty = true
|
72
|
+
@data[index] = value
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# generic getter for column values
|
77
|
+
def get_column( column )
|
78
|
+
table = self.class.table
|
79
|
+
index = table.column_order.index( column.to_sym )
|
80
|
+
if table.columns[column.to_sym].respond_to?( :filter_get )
|
81
|
+
table.columns[column.to_sym].filter_get( @data[index] )
|
82
|
+
else
|
83
|
+
@data[index]
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
61
87
|
end
|
62
88
|
|
63
89
|
end
|
data/lib/momomoto/table.rb
CHANGED
@@ -9,18 +9,31 @@ module Momomoto
|
|
9
9
|
|
10
10
|
# set the default order for selects
|
11
11
|
def default_order=( order )
|
12
|
-
|
12
|
+
class_variable_set( :@@default_order, order )
|
13
13
|
end
|
14
14
|
|
15
|
-
# get
|
15
|
+
# get the columns of the table this class operates on
|
16
16
|
def default_order( order = nil )
|
17
17
|
return self.default_order=( order ) if order
|
18
|
-
|
18
|
+
begin
|
19
|
+
class_variable_get( :@@default_order )
|
20
|
+
rescue NameError
|
21
|
+
class_variable_set( :@@default_order, nil )
|
22
|
+
end
|
19
23
|
end
|
20
24
|
|
21
25
|
# set the columns of the table this class operates on
|
22
26
|
def columns=( columns )
|
23
27
|
class_variable_set( :@@columns, columns)
|
28
|
+
# we store the order separate because it's quite important
|
29
|
+
# that it's constant otherwise get_colum and set_column
|
30
|
+
# on the row class might stop working
|
31
|
+
class_variable_set( :@@column_order, columns.keys )
|
32
|
+
end
|
33
|
+
|
34
|
+
# get the columns of this table
|
35
|
+
def column_order
|
36
|
+
class_variable_get( :@@column_order )
|
24
37
|
end
|
25
38
|
|
26
39
|
# get the columns of the table this class operates on
|
@@ -117,6 +130,35 @@ module Momomoto
|
|
117
130
|
data
|
118
131
|
end
|
119
132
|
|
133
|
+
## Searches for records and returns an array containing the records
|
134
|
+
def select_with_join( conditions = {}, options = {} )
|
135
|
+
initialize_table unless class_variables.member?('@@initialized')
|
136
|
+
join_table = options[:join]
|
137
|
+
fields = columns.keys.map{|field| full_name+'."'+field.to_s+'"'}
|
138
|
+
fields += join_table.columns.keys.map{|field| join_table.full_name+'."'+field.to_s+'"'}
|
139
|
+
|
140
|
+
sql = "SELECT " + fields.join( "," ) + " FROM "
|
141
|
+
sql += full_name
|
142
|
+
sql += " LEFT OUTER JOIN " + join_table.full_name + " USING(#{join_columns(join_table).join(',')})"
|
143
|
+
sql += compile_where( conditions )
|
144
|
+
sql += compile_order( options[:order] ) if options[:order]
|
145
|
+
sql += compile_limit( options[:limit] ) if options[:limit]
|
146
|
+
sql += compile_offset( options[:offset] ) if options[:offset]
|
147
|
+
data = []
|
148
|
+
database.execute( sql ).each do | row |
|
149
|
+
new_row = const_get(:Row).new( row[0, columns.keys.length] )
|
150
|
+
join_row = join_table.const_get(:Row).new( row[columns.keys.length,join_table.columns.keys.length] )
|
151
|
+
new_row.instance_variable_set(:@join, join_row)
|
152
|
+
new_row.send( :instance_eval ) { class << self; self; end }.send(:define_method, join_table.table_name ) do join_row end
|
153
|
+
data << new_row
|
154
|
+
end
|
155
|
+
data
|
156
|
+
end
|
157
|
+
|
158
|
+
def join_columns( join_table )
|
159
|
+
join_table.primary_keys.select{|f| columns.key?(f)}
|
160
|
+
end
|
161
|
+
|
120
162
|
## constructor for a record in this table accepts a hash with presets for the fields of the record
|
121
163
|
def new( fields = {} )
|
122
164
|
initialize_table unless class_variables.member?('@@initialized')
|
data/test/test.sql
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
|
2
|
+
CREATE TABLE conference (
|
3
|
+
conference_id SERIAL,
|
4
|
+
acronym TEXT NOT NULL UNIQUE,
|
5
|
+
title TEXT NOT NULL,
|
6
|
+
subtitle TEXT,
|
7
|
+
description TEXT,
|
8
|
+
start_date DATE NOT NULL,
|
9
|
+
timeslot_duration INTERVAL NOT NULL DEFAULT '0:30:00',
|
10
|
+
default_timeslots INTEGER NOT NULL DEFAULT 1,
|
11
|
+
max_timeslots_per_event INTEGER NOT NULL DEFAULT 10,
|
12
|
+
day_change TIME WITHOUT TIME ZONE NOT NULL DEFAULT '0:00:00',
|
13
|
+
feedback_enabled BOOL NOT NULL DEFAULT FALSE,
|
14
|
+
submission_enabled BOOL NOT NULL DEFAULT FALSE,
|
15
|
+
visitor_enabled BOOL NOT NULL DEFAULT FALSE,
|
16
|
+
reconfirmation_enabled BOOL NOT NULL DEFAULT FALSE,
|
17
|
+
PRIMARY KEY(conference_id)
|
18
|
+
);
|
19
|
+
|
20
|
+
CREATE TABLE person (
|
21
|
+
person_id SERIAL,
|
22
|
+
first_name TEXT,
|
23
|
+
last_name TEXT,
|
24
|
+
nick_name TEXT,
|
25
|
+
PRIMARY KEY(person_id)
|
26
|
+
);
|
27
|
+
|
28
|
+
INSERT INTO person(nick_name) VALUES ('blossom');
|
29
|
+
INSERT INTO person(nick_name) VALUES ('buttercup');
|
30
|
+
INSERT INTO person(nick_name) VALUES ('bubbles');
|
31
|
+
INSERT INTO person(nick_name) VALUES ('mojojojo');
|
32
|
+
|
33
|
+
CREATE TABLE event (
|
34
|
+
event_id SERIAL,
|
35
|
+
title TEXT,
|
36
|
+
subtitle TEXT,
|
37
|
+
PRIMARY KEY(event_id)
|
38
|
+
);
|
39
|
+
|
40
|
+
CREATE TABLE event_person (
|
41
|
+
event_id INTEGER,
|
42
|
+
person_id INTEGER,
|
43
|
+
description TEXT,
|
44
|
+
FOREIGN KEY(event_id) REFERENCES event(event_id),
|
45
|
+
FOREIGN KEY(person_id) REFERENCES person(person_id),
|
46
|
+
PRIMARY KEY(event_id, person_id, description)
|
47
|
+
);
|
48
|
+
|
49
|
+
CREATE TABLE test_bigint( id SERIAL, data BIGINT, PRIMARY KEY(id));
|
50
|
+
CREATE TABLE test_boolean( id SERIAL, data BOOLEAN, PRIMARY KEY(id));
|
51
|
+
CREATE TABLE test_bytea( id SERIAL, data BYTEA, PRIMARY KEY(id));
|
52
|
+
CREATE TABLE test_character( id SERIAL, data CHARACTER(1000), PRIMARY KEY(id));
|
53
|
+
CREATE TABLE test_character_varying( id SERIAL, data VARCHAR(1000), PRIMARY KEY(id));
|
54
|
+
CREATE TABLE test_date( id SERIAL, data DATE, PRIMARY KEY(id));
|
55
|
+
CREATE TABLE test_inet( id SERIAL, data INET, PRIMARY KEY(id));
|
56
|
+
CREATE TABLE test_integer( id SERIAL, data INTEGER, PRIMARY KEY(id));
|
57
|
+
CREATE TABLE test_interval( id SERIAL, data INTERVAL, PRIMARY KEY(id));
|
58
|
+
CREATE TABLE test_nodefault ( id INTEGER, data TEXT, PRIMARY KEY(id));
|
59
|
+
CREATE TABLE test_numeric( id SERIAL, data NUMERIC, PRIMARY KEY(id));
|
60
|
+
CREATE TABLE test_real( id SERIAL, data REAL, PRIMARY KEY(id));
|
61
|
+
CREATE TABLE test_smallint( id SERIAL, data SMALLINT, PRIMARY KEY(id));
|
62
|
+
CREATE TABLE test_text( id SERIAL, data TEXT, PRIMARY KEY(id));
|
63
|
+
CREATE TABLE test_time_with_time_zone( id SERIAL, data TIME WITH TIME ZONE, PRIMARY KEY(id));
|
64
|
+
CREATE TABLE test_time_without_time_zone( id SERIAL, data TIME WITHOUT TIME ZONE, PRIMARY KEY(id));
|
65
|
+
CREATE TABLE test_timestamp_with_time_zone( id SERIAL, data TIMESTAMP WITH TIME ZONE, PRIMARY KEY(id));
|
66
|
+
CREATE TABLE test_timestamp_without_time_zone( id SERIAL, data TIMESTAMP WITHOUT TIME ZONE, PRIMARY KEY(id));
|
67
|
+
|
68
|
+
|
data/test/test_base.rb
CHANGED
@@ -8,9 +8,9 @@ class TestBase < Test::Unit::TestCase
|
|
8
8
|
assert_equal( " WHERE person_id = '1'" , t.compile_where( :person_id => '1' ) )
|
9
9
|
assert_equal( " WHERE person_id IN ('1')" , t.compile_where( :person_id => ['1'] ) )
|
10
10
|
assert_equal( " WHERE person_id IN ('1','2')" , t.compile_where( :person_id => ['1',2] ) )
|
11
|
-
assert_equal( " WHERE first_name = '1'" , t.compile_where( :first_name => '1' ) )
|
12
|
-
assert_equal( " WHERE first_name = 'chu''nky'" , t.compile_where( :first_name => "chu'nky" ) )
|
13
|
-
assert_equal( " WHERE first_name IN ('chu''nky','bac''n')" , t.compile_where( :first_name => ["chu'nky","bac'n"] ) )
|
11
|
+
assert_equal( " WHERE first_name = E'1'" , t.compile_where( :first_name => '1' ) )
|
12
|
+
assert_equal( " WHERE first_name = E'chu''nky'" , t.compile_where( :first_name => "chu'nky" ) )
|
13
|
+
assert_equal( " WHERE first_name IN (E'chu''nky',E'bac''n')" , t.compile_where( :first_name => ["chu'nky","bac'n"] ) )
|
14
14
|
end
|
15
15
|
|
16
16
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
|
2
|
+
CREATE OR REPLACE FUNCTION test_parameter_sql( param1 INTEGER ) RETURNS INTEGER AS $$
|
3
|
+
SELECT $1;
|
4
|
+
$$ LANGUAGE sql;
|
5
|
+
|
6
|
+
CREATE OR REPLACE FUNCTION test_parameter_plpgsql( param1 INTEGER, param2 TEXT ) RETURNS INTEGER AS $$
|
7
|
+
DECLARE
|
8
|
+
BEGIN
|
9
|
+
RETURN param1;
|
10
|
+
END;
|
11
|
+
$$ LANGUAGE plpgsql;
|
12
|
+
|
13
|
+
CREATE OR REPLACE FUNCTION test_set_returning( person_id INTEGER ) RETURNS SETOF person AS $$
|
14
|
+
DECLARE
|
15
|
+
result RECORD;
|
16
|
+
BEGIN
|
17
|
+
FOR result IN
|
18
|
+
SELECT person.* FROM person WHERE person.person_id <> person_id
|
19
|
+
LOOP
|
20
|
+
RETURN NEXT result;
|
21
|
+
END LOOP;
|
22
|
+
RETURN;
|
23
|
+
END;
|
24
|
+
$$ LANGUAGE plpgsql;
|
25
|
+
|
@@ -17,10 +17,8 @@ class TestInformationSchema < Test::Unit::TestCase
|
|
17
17
|
|
18
18
|
# test for working information_schema.table_constraints class
|
19
19
|
def test_information_schema_table_constraints
|
20
|
-
a = Momomoto::Information_schema::Table_constraints.select(:table_schema => 'pg_catalog', :table_name => 'pg_class')
|
21
|
-
assert_equal( 0, a.length )
|
22
20
|
a = Momomoto::Information_schema::Table_constraints.select(:table_schema => 'public')
|
23
|
-
|
21
|
+
assert( a.length > 0 )
|
24
22
|
end
|
25
23
|
|
26
24
|
end
|
data/test/test_table.rb
CHANGED
@@ -2,18 +2,32 @@
|
|
2
2
|
class TestTable < Test::Unit::TestCase
|
3
3
|
|
4
4
|
class Person < Momomoto::Table
|
5
|
-
|
6
|
-
newvalue
|
7
|
-
|
8
|
-
|
9
|
-
def self.nick_name=( row, newvalue )
|
10
|
-
row.first_name + newvalue
|
5
|
+
module Methods
|
6
|
+
def nick_name=( newvalue )
|
7
|
+
set_column(:nick_name, get_column(:first_name) + newvalue )
|
8
|
+
end
|
11
9
|
end
|
12
10
|
end
|
13
11
|
|
14
12
|
class Conference < Momomoto::Table
|
15
13
|
end
|
16
14
|
|
15
|
+
def test_default_order_getter
|
16
|
+
c1 = Class.new( Momomoto::Table )
|
17
|
+
c1.default_order = [:a,:b]
|
18
|
+
assert_equal( [:a,:b], c1.default_order )
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_default_order_setter
|
22
|
+
c1 = Class.new( Momomoto::Table )
|
23
|
+
c1.default_order = :a
|
24
|
+
assert_equal( :a, c1.default_order )
|
25
|
+
c1.default_order = :b
|
26
|
+
assert_equal( :b, c1.default_order )
|
27
|
+
c1.default_order( :c )
|
28
|
+
assert_equal( :c, c1.default_order )
|
29
|
+
end
|
30
|
+
|
17
31
|
def test_columns_getter
|
18
32
|
c1 = Class.new( Momomoto::Table )
|
19
33
|
c1.columns = {:a=>Momomoto::Datatype::Text}
|
@@ -109,6 +123,14 @@ class TestTable < Test::Unit::TestCase
|
|
109
123
|
assert_equal( true, sven.new_record? )
|
110
124
|
end
|
111
125
|
|
126
|
+
def test_offset
|
127
|
+
p = Person.select( {}, {:limit => 1, :order => :person_id,:offset=>2})
|
128
|
+
assert_operator( 2, :<, p[0].person_id )
|
129
|
+
assert_raise( Momomoto::Error ) do
|
130
|
+
Person.select( {}, {:limit => 1, :order => :person_id,:offset=>'bacon'})
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
112
134
|
def test_select
|
113
135
|
r = Person.select( nil, {:limit => 3})
|
114
136
|
assert_equal( 3, r.length )
|
metadata
CHANGED
@@ -3,7 +3,7 @@ rubygems_version: 0.8.11
|
|
3
3
|
specification_version: 1
|
4
4
|
name: momomoto
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 0.1.
|
6
|
+
version: 0.1.1
|
7
7
|
date: 2007-08-13 00:00:00 +02:00
|
8
8
|
summary: Momomoto is an object relational mapper for PostgreSQL.
|
9
9
|
require_paths:
|
@@ -90,6 +90,8 @@ files:
|
|
90
90
|
- test/test_datatype.rb
|
91
91
|
- test/test_bigint.rb
|
92
92
|
- test/test_date.rb
|
93
|
+
- test/test_functions.sql
|
94
|
+
- test/test.sql
|
93
95
|
- LICENSE
|
94
96
|
- Rakefile
|
95
97
|
test_files: []
|
@@ -103,7 +105,7 @@ executables: []
|
|
103
105
|
extensions: []
|
104
106
|
|
105
107
|
requirements:
|
106
|
-
- PostgreSQL 8.1.
|
108
|
+
- PostgreSQL 8.1.x or greater
|
107
109
|
dependencies:
|
108
110
|
- !ruby/object:Gem::Dependency
|
109
111
|
name: ruby-postgres
|