og 0.12.0 → 0.13.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.
- data/CHANGELOG +27 -0
- data/INSTALL +56 -0
- data/{README.og → README} +3 -3
- data/Rakefile +7 -73
- data/benchmark/bench.rb +75 -0
- data/benchmark/sqlite-no-prepare.1.txt +13 -0
- data/benchmark/sqlite-no-prepare.2.txt +13 -0
- data/benchmark/sqlite-prepare.1.txt +13 -0
- data/benchmark/sqlite-prepare.2.txt +13 -0
- data/doc/AUTHORS +0 -9
- data/{RELEASES.og → doc/RELEASES} +15 -0
- data/doc/config.txt +35 -0
- data/doc/tutorial.txt +595 -0
- data/examples/{og/README → README} +1 -1
- data/examples/{og/mock_example.rb → mock_example.rb} +1 -1
- data/examples/{og/mysql_to_psql.rb → mysql_to_psql.rb} +1 -1
- data/examples/{og/run.rb → run.rb} +1 -1
- data/install.rb +8 -5
- data/lib/og.rb +13 -8
- data/lib/og/adapter.rb +1 -1
- data/lib/og/adapters/filesys.rb +1 -1
- data/lib/og/adapters/mysql.rb +4 -3
- data/lib/og/adapters/oracle.rb +1 -1
- data/lib/og/adapters/psql.rb +4 -3
- data/lib/og/adapters/sqlite.rb +1 -1
- data/lib/og/backend.rb +1 -1
- data/lib/og/connection.rb +1 -1
- data/lib/og/database.rb +1 -1
- data/lib/og/meta.rb +13 -2
- data/lib/og/observer.rb +1 -1
- data/lib/og/typemacros.rb +1 -1
- data/lib/og/validation.rb +81 -0
- data/test/og/tc_validation.rb +89 -0
- metadata +33 -65
- data/ChangeLog +0 -1549
- data/lib/glue.rb +0 -55
- data/lib/glue/array.rb +0 -61
- data/lib/glue/attribute.rb +0 -83
- data/lib/glue/cache.rb +0 -138
- data/lib/glue/flexob.rb +0 -12
- data/lib/glue/hash.rb +0 -122
- data/lib/glue/inflector.rb +0 -91
- data/lib/glue/logger.rb +0 -147
- data/lib/glue/misc.rb +0 -14
- data/lib/glue/mixins.rb +0 -36
- data/lib/glue/number.rb +0 -24
- data/lib/glue/object.rb +0 -32
- data/lib/glue/pool.rb +0 -60
- data/lib/glue/property.rb +0 -408
- data/lib/glue/string.rb +0 -162
- data/lib/glue/time.rb +0 -85
- data/lib/glue/validation.rb +0 -394
- data/vendor/extensions/_base.rb +0 -153
- data/vendor/extensions/_template.rb +0 -36
- data/vendor/extensions/all.rb +0 -21
- data/vendor/extensions/array.rb +0 -68
- data/vendor/extensions/binding.rb +0 -224
- data/vendor/extensions/class.rb +0 -50
- data/vendor/extensions/continuation.rb +0 -71
- data/vendor/extensions/enumerable.rb +0 -250
- data/vendor/extensions/hash.rb +0 -23
- data/vendor/extensions/io.rb +0 -58
- data/vendor/extensions/kernel.rb +0 -42
- data/vendor/extensions/module.rb +0 -114
- data/vendor/extensions/numeric.rb +0 -230
- data/vendor/extensions/object.rb +0 -164
- data/vendor/extensions/ostruct.rb +0 -41
- data/vendor/extensions/string.rb +0 -316
- data/vendor/extensions/symbol.rb +0 -28
data/CHANGELOG
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
17-03-2005 George Moschovitis <gm@navel.gr>
|
2
|
+
|
3
|
+
* Rakefile: updated.
|
4
|
+
|
5
|
+
* test/*: changes to make the tests pass again.
|
6
|
+
|
7
|
+
15-03-2005 George Moschovitis <gm@navel.gr>
|
8
|
+
|
9
|
+
* README: updated.
|
10
|
+
|
11
|
+
12-03-2005 George Moschovitis <gm@navel.gr>
|
12
|
+
|
13
|
+
* lib/og/validation.rb: introduced,
|
14
|
+
(#validate_unique): implemented,
|
15
|
+
(#validate_related): implemented.
|
16
|
+
|
17
|
+
10-03-2005 George Moschovitis <gm@navel.gr>
|
18
|
+
|
19
|
+
* lib/og.rb: removed Name.
|
20
|
+
|
21
|
+
* install.rb: updated for Og.
|
22
|
+
|
23
|
+
* INSTALL: updated for Og.
|
24
|
+
|
25
|
+
* Rakefile: updated, removed nitro dependencies.
|
26
|
+
|
27
|
+
* CHANGELOG: splitted from the Nitro ChangeLog.
|
data/INSTALL
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
= Instalation with RubyGems
|
2
|
+
|
3
|
+
1. Download and install RubyGems:
|
4
|
+
|
5
|
+
http://rubygems.rubyforge.org
|
6
|
+
|
7
|
+
2. Install the distribution:
|
8
|
+
|
9
|
+
gem install og
|
10
|
+
|
11
|
+
When asked about the dependencies to include, only accept
|
12
|
+
the dependencies for the RDBMS backends you are planning
|
13
|
+
to use.
|
14
|
+
|
15
|
+
3. Set environment variable (required to load RubyGems):
|
16
|
+
|
17
|
+
export RUBYOPT=-rubygems
|
18
|
+
|
19
|
+
You can add this in you .bashrc in Unix.
|
20
|
+
|
21
|
+
Alternatively you can run your applications with the -rubygem
|
22
|
+
option:
|
23
|
+
|
24
|
+
ruby -rubygem xxx.rb
|
25
|
+
|
26
|
+
= Installation without RubyGems using script.
|
27
|
+
|
28
|
+
Installation without RubyGems is *strongly* discouraged.
|
29
|
+
However, as Og is all about freedom and possibilities,
|
30
|
+
a standard installation script is provided.
|
31
|
+
|
32
|
+
1. Switch to an administrator account
|
33
|
+
|
34
|
+
For example in Unix:
|
35
|
+
|
36
|
+
$ su -
|
37
|
+
|
38
|
+
2. Run the installation script.
|
39
|
+
|
40
|
+
$ ruby install.rb
|
41
|
+
|
42
|
+
This installation script also installs some vendor libraries
|
43
|
+
that you possibly have allready installed. Use with caution.
|
44
|
+
|
45
|
+
= Manual installation.
|
46
|
+
|
47
|
+
Uncompress your distribution (Unix example):
|
48
|
+
|
49
|
+
$ cd my_dir
|
50
|
+
$ tar xvfz og-x.x.x.tar.gz
|
51
|
+
|
52
|
+
Put the libray dir in the Ruby path (Unix example):
|
53
|
+
|
54
|
+
$ export RUBYOPT='-I path/to/og/lib'
|
55
|
+
|
56
|
+
|
data/{README.og → README}
RENAMED
@@ -1,6 +1,6 @@
|
|
1
|
-
= Og 0.
|
1
|
+
= Og 0.13.0
|
2
2
|
|
3
|
-
(ObjectGraph) is a powerfull object-relational mapping library. Og provides
|
3
|
+
Og (ObjectGraph) is a powerfull object-relational mapping library. Og provides
|
4
4
|
transparent serialization of object graphs to a RDBMS
|
5
5
|
backend. Unlike other similar libraries Og maps standard Ruby
|
6
6
|
objects to SQL tables and not vice versa. Og provides a meta language
|
@@ -18,7 +18,7 @@ PostgreSQL, MySQL and SQLite are included.
|
|
18
18
|
|
19
19
|
Og is part of the Nitro project, released as a stand-alone library
|
20
20
|
due to popular demand. You can find the ChangeLog in the Nitro
|
21
|
-
distribution (http://
|
21
|
+
distribution (http://nitro.rubyforge.org).
|
22
22
|
|
23
23
|
|
24
24
|
== Features
|
data/Rakefile
CHANGED
@@ -6,21 +6,11 @@ require 'rake/rdoctask'
|
|
6
6
|
require 'rake/testtask'
|
7
7
|
require 'rake/gempackagetask'
|
8
8
|
|
9
|
-
og = true
|
10
|
-
|
11
9
|
task :default => :package
|
12
10
|
|
13
11
|
# Run the tests.
|
14
12
|
|
15
13
|
Rake::TestTask.new do |t|
|
16
|
-
t.libs << 'test'
|
17
|
-
t.test_files = FileList['test/**/tc*.rb'].exclude('**/tc*og*.rb').exclude('test/og/**/*')
|
18
|
-
t.verbose = true
|
19
|
-
end
|
20
|
-
|
21
|
-
# Run all tests. (including expensive/depended tests)
|
22
|
-
|
23
|
-
Rake::TestTask.new(:test_all) do |t|
|
24
14
|
t.libs << 'test'
|
25
15
|
t.test_files = FileList['test/**/tc*.rb']
|
26
16
|
t.verbose = true
|
@@ -31,14 +21,12 @@ end
|
|
31
21
|
Rake::RDocTask.new do |rd|
|
32
22
|
rd.main = 'README'
|
33
23
|
rd.rdoc_dir = 'doc/rdoc'
|
34
|
-
rd.rdoc_files.include('README', 'INSTALL', 'doc/
|
24
|
+
rd.rdoc_files.include('README', 'INSTALL', 'doc/config.txt', 'doc/tutorial.txt', 'lib/**/*.rb')
|
35
25
|
rd.options << '--all --inline-source'
|
36
26
|
end
|
37
27
|
|
38
28
|
# Build gem.
|
39
29
|
|
40
|
-
if og
|
41
|
-
|
42
30
|
spec = Gem::Specification.new do |s|
|
43
31
|
s.name = 'og'
|
44
32
|
if File.read('lib/og.rb') =~ /Version\s+=\s+'(\d+\.\d+\.\d+)'/
|
@@ -49,86 +37,32 @@ spec = Gem::Specification.new do |s|
|
|
49
37
|
s.summary = 'Og (ObjectGraph)'
|
50
38
|
s.description = 'An efficient and transparent Object-Relational mapping library'
|
51
39
|
|
40
|
+
s.add_dependency 'glue', '= 0.13.0'
|
52
41
|
# s.add_dependency 'postgres-pr', '>= 0.3.0'
|
53
42
|
# s.add_dependency 'postgres', '>= 0.7.1'
|
54
|
-
s.add_dependency 'extensions', '>= 0.5'
|
55
43
|
# s.add_dependency 'sqlite3-ruby', '>= 1.0.0'
|
56
44
|
#s.add_dependency 'mysql', '>= 2.5.1'
|
57
|
-
s.add_dependency 'flexmock', '>= 0.0.3'
|
58
45
|
|
59
|
-
s.required_ruby_version = '>= 1.8.
|
46
|
+
s.required_ruby_version = '>= 1.8.1'
|
60
47
|
|
61
48
|
s.files = FileList[
|
62
|
-
'
|
63
|
-
'install.rb',
|
64
|
-
'examples/og/*', 'lib/glue.rb', 'lib/glue/**/*', 'lib/og/**/*', 'lib/og.rb',
|
65
|
-
'test/*og*.rb', 'test/og/*', 'vendor/extensions/**/*'
|
49
|
+
'[A-Z]*', 'install.rb', '{bin,benchmark,examples,doc,lib,test,vendor}/**/*'
|
66
50
|
].exclude('.svn/**/*').exclude('**/*.log').to_a
|
67
51
|
|
68
52
|
s.require_path = 'lib'
|
69
53
|
s.autorequire = 'og'
|
70
54
|
|
71
55
|
s.has_rdoc = true
|
72
|
-
s.extra_rdoc_files = FileList['README
|
73
|
-
s.rdoc_options << '--main' << 'README
|
56
|
+
s.extra_rdoc_files = FileList['README', 'doc/RELEASES', 'doc/LICENSE', 'doc/AUTHORS'].to_a
|
57
|
+
s.rdoc_options << '--main' << 'README' << '--title' << 'Og Documentation'
|
74
58
|
s.rdoc_options << '--all' << '--inline-source'
|
75
59
|
|
76
60
|
s.author = 'George Moschovitis'
|
77
61
|
s.email = 'gm@navel.gr'
|
78
|
-
s.homepage = 'http://
|
62
|
+
s.homepage = 'http://nitro.rubyforge.org'
|
79
63
|
s.rubyforge_project = 'nitro'
|
80
64
|
end
|
81
65
|
|
82
|
-
else
|
83
|
-
|
84
|
-
spec = Gem::Specification.new do |s|
|
85
|
-
s.name = 'nitro'
|
86
|
-
if File.read('lib/nitro.rb') =~ /Version\s+=\s+'(\d+\.\d+\.\d+)'/
|
87
|
-
s.version = $1
|
88
|
-
else
|
89
|
-
raise 'No version found'
|
90
|
-
end
|
91
|
-
s.summary = 'Nitro Web Engine'
|
92
|
-
s.description =
|
93
|
-
'An efficient, multiparadigm and flexible platform for rapid ' +
|
94
|
-
'web application development. Implements a full development stack.'
|
95
|
-
|
96
|
-
# s.add_dependency 'postgres-pr', '>= 0.3.0'
|
97
|
-
# s.add_dependency 'postgres', '>= 0.7.1'
|
98
|
-
# s.add_dependency 'ParseTree', '>= 1.3.3'
|
99
|
-
s.add_dependency 'extensions', '>= 0.5'
|
100
|
-
# s.add_dependency 'sqlite3-ruby', '>= 1.0.0'
|
101
|
-
# s.add_dependency 'mysql', '>= 2.5.1'
|
102
|
-
s.add_dependency 'flexmock', '>= 0.0.3'
|
103
|
-
|
104
|
-
s.required_ruby_version = '>= 1.8.0'
|
105
|
-
|
106
|
-
s.files = FileList[
|
107
|
-
'[A-Z]*', 'install.rb', '{bin,benchmark,examples,doc,lib,test,vendor}/**/*'
|
108
|
-
].exclude('.svn/**/*').exclude('*.og').exclude('**/*.log').to_a
|
109
|
-
|
110
|
-
s.require_path = 'lib'
|
111
|
-
s.autorequire = 'nitro'
|
112
|
-
|
113
|
-
s.has_rdoc = true
|
114
|
-
s.extra_rdoc_files = FileList['[A-Z]*'].exclude('*.og').to_a
|
115
|
-
s.rdoc_options << '--main' << 'README' << '--title' << 'Nitro Documentation'
|
116
|
-
s.rdoc_options << '--all' << '--inline-source'
|
117
|
-
|
118
|
-
s.test_files = []
|
119
|
-
|
120
|
-
s.bindir = 'bin'
|
121
|
-
s.executables = ['nitro']
|
122
|
-
s.default_executable = 'nitro'
|
123
|
-
|
124
|
-
s.author = 'George Moschovitis'
|
125
|
-
s.email = 'gm@navel.gr'
|
126
|
-
s.homepage = 'http://www.rubyforge.com/projects/nitro'
|
127
|
-
s.rubyforge_project = 'nitro'
|
128
|
-
end
|
129
|
-
|
130
|
-
end
|
131
|
-
|
132
66
|
Rake::GemPackageTask.new(spec) do |pkg|
|
133
67
|
pkg.package_dir = 'dist'
|
134
68
|
pkg.need_zip = true
|
data/benchmark/bench.rb
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
# * George Moschovitis <gm@navel.gr>
|
2
|
+
# (c) 2004-2005 Navel, all rights reserved.
|
3
|
+
# $Id: bench.rb 288 2005-03-10 12:47:03Z gmosx $
|
4
|
+
|
5
|
+
require 'og'; include Og
|
6
|
+
|
7
|
+
|
8
|
+
config = {
|
9
|
+
:adapter => 'sqlite',
|
10
|
+
:database => 'test',
|
11
|
+
:connection_count => 5
|
12
|
+
}
|
13
|
+
|
14
|
+
class Article
|
15
|
+
prop_accessor :title, String
|
16
|
+
prop_accessor :body, String
|
17
|
+
prop_accessor :hits, Fixnum
|
18
|
+
prop_accessor :rate, Fixnum
|
19
|
+
|
20
|
+
def initialize(title = nil, body = nil)
|
21
|
+
@title = title
|
22
|
+
@body = body
|
23
|
+
@hits = rand(5)
|
24
|
+
@rate = rand(100)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
Database.drop_db!(config)
|
29
|
+
db = Database.new(config)
|
30
|
+
|
31
|
+
# Benchmark the insert speed. Useful for finding
|
32
|
+
# the improvement when using prepared statements.
|
33
|
+
|
34
|
+
articles = []
|
35
|
+
|
36
|
+
500.times do |i|
|
37
|
+
articles << Article.new("Title#{i}", "Body#{i}")
|
38
|
+
end
|
39
|
+
|
40
|
+
sum = 0
|
41
|
+
min = 999999
|
42
|
+
max = -min
|
43
|
+
|
44
|
+
GC.disable
|
45
|
+
|
46
|
+
10.times do |i|
|
47
|
+
|
48
|
+
db.exec "DELETE FROM #{Article::DBTABLE}"
|
49
|
+
|
50
|
+
for article in articles
|
51
|
+
article.oid = nil
|
52
|
+
end
|
53
|
+
|
54
|
+
Article.create("Dummy", "Dummy")
|
55
|
+
|
56
|
+
t1 = Time.now
|
57
|
+
articles.each do |a|
|
58
|
+
a.save!
|
59
|
+
end
|
60
|
+
t2 = Time.now
|
61
|
+
|
62
|
+
d = t2 - t1
|
63
|
+
sum += d
|
64
|
+
min = d if d < min
|
65
|
+
max = d if d > max
|
66
|
+
|
67
|
+
puts "Insert test #{i}: #{d} seconds"
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
puts %{
|
72
|
+
Min: #{min}
|
73
|
+
Max: #{max}
|
74
|
+
Average: #{sum/10}
|
75
|
+
}
|
@@ -0,0 +1,13 @@
|
|
1
|
+
Insert test 0: 3.53403 seconds
|
2
|
+
Insert test 1: 4.222878 seconds
|
3
|
+
Insert test 2: 3.84379 seconds
|
4
|
+
Insert test 3: 3.877513 seconds
|
5
|
+
Insert test 4: 3.793392 seconds
|
6
|
+
Insert test 5: 3.725927 seconds
|
7
|
+
Insert test 6: 3.814542 seconds
|
8
|
+
Insert test 7: 3.760054 seconds
|
9
|
+
Insert test 8: 3.936868 seconds
|
10
|
+
Insert test 9: 4.061833 seconds
|
11
|
+
Min: 3.53403
|
12
|
+
Max: 4.222878
|
13
|
+
Average: 3.8570827
|
@@ -0,0 +1,13 @@
|
|
1
|
+
Insert test 0: 4.050384 seconds
|
2
|
+
Insert test 1: 4.098912 seconds
|
3
|
+
Insert test 2: 3.739213 seconds
|
4
|
+
Insert test 3: 4.030227 seconds
|
5
|
+
Insert test 4: 4.061516 seconds
|
6
|
+
Insert test 5: 3.963748 seconds
|
7
|
+
Insert test 6: 4.088462 seconds
|
8
|
+
Insert test 7: 4.047734 seconds
|
9
|
+
Insert test 8: 3.966496 seconds
|
10
|
+
Insert test 9: 3.948346 seconds
|
11
|
+
Min: 3.739213
|
12
|
+
Max: 4.098912
|
13
|
+
Average: 3.9995038
|
@@ -0,0 +1,13 @@
|
|
1
|
+
Insert test 0: 3.578891 seconds
|
2
|
+
Insert test 1: 4.257714 seconds
|
3
|
+
Insert test 2: 3.535036 seconds
|
4
|
+
Insert test 3: 3.742329 seconds
|
5
|
+
Insert test 4: 3.874829 seconds
|
6
|
+
Insert test 5: 3.608657 seconds
|
7
|
+
Insert test 6: 3.843804 seconds
|
8
|
+
Insert test 7: 3.688756 seconds
|
9
|
+
Insert test 8: 3.858303 seconds
|
10
|
+
Insert test 9: 3.739155 seconds
|
11
|
+
Min: 3.535036
|
12
|
+
Max: 4.257714
|
13
|
+
Average: 3.7727474
|
@@ -0,0 +1,13 @@
|
|
1
|
+
Insert test 0: 3.859916 seconds
|
2
|
+
Insert test 1: 4.321912 seconds
|
3
|
+
Insert test 2: 3.836067 seconds
|
4
|
+
Insert test 3: 4.077798 seconds
|
5
|
+
Insert test 4: 4.171512 seconds
|
6
|
+
Insert test 5: 4.148027 seconds
|
7
|
+
Insert test 6: 3.841498 seconds
|
8
|
+
Insert test 7: 3.94367 seconds
|
9
|
+
Insert test 8: 3.758128 seconds
|
10
|
+
Insert test 9: 4.25072 seconds
|
11
|
+
Min: 3.758128
|
12
|
+
Max: 4.321912
|
13
|
+
Average: 4.0209248
|
data/doc/AUTHORS
CHANGED
@@ -18,14 +18,5 @@ IDEAS, ADDITIONAL CODING, SUPPORT:
|
|
18
18
|
* Elias Athanasopoulos <elathan@navel.gr>
|
19
19
|
Additional coding.
|
20
20
|
|
21
|
-
* Gavin Sinclair <gsinclair@soyabean.com.au>
|
22
|
-
Logger#trace method.
|
23
|
-
|
24
21
|
* Thomas Quas <tquas@yahoo.com>
|
25
22
|
Ideas, bug reports, unit tests.
|
26
|
-
|
27
|
-
* Kostas Nasis <kostas@nasis.com>
|
28
|
-
Ideas and bug reports.
|
29
|
-
|
30
|
-
* Elias Karakoulakis <ekarak@navel.gr>
|
31
|
-
Additional design.
|
@@ -1,3 +1,18 @@
|
|
1
|
+
== Version 0.13.0 was released on 17/03/2005.
|
2
|
+
|
3
|
+
A maintenance release.
|
4
|
+
|
5
|
+
Most notable additons:
|
6
|
+
|
7
|
+
* Better separated from Nitro.
|
8
|
+
|
9
|
+
* Database related validations (validate_unique) etc.
|
10
|
+
|
11
|
+
* Emmit warnings on implicit graph changes.
|
12
|
+
|
13
|
+
* Many bugfixes.
|
14
|
+
|
15
|
+
|
1
16
|
== Version 0.12.0 was released on 07/03/2005.
|
2
17
|
|
3
18
|
A careful blend of new features and subtle improvements
|
data/doc/config.txt
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
= Og Configuration parameters
|
2
|
+
|
3
|
+
This file presents a complete list of Og configuration
|
4
|
+
parameters.
|
5
|
+
|
6
|
+
=== Og.table_prefix = nil
|
7
|
+
|
8
|
+
Attach the given prefix to all generated SQL table names.
|
9
|
+
Usefull on hosting scenarios where you have to run multiple
|
10
|
+
applications/sites on a single database.
|
11
|
+
|
12
|
+
=== Og.auto_manage_classes = true
|
13
|
+
|
14
|
+
If true, use Ruby's advanced introspection capabilities to
|
15
|
+
automatically manage classes that define properties.
|
16
|
+
|
17
|
+
=== Og.enchant_managed_classes = true
|
18
|
+
|
19
|
+
If true, the library automatically 'enchants' managed classes.
|
20
|
+
In enchant mode, special db aware methods are added to
|
21
|
+
managed classes and instances. If false, Og enchants only classes
|
22
|
+
that define properties.
|
23
|
+
|
24
|
+
=== Og.create_schema = true
|
25
|
+
|
26
|
+
If set to true, Og attempts to recreate the database schema
|
27
|
+
to store the managed objects. If the table for an object class
|
28
|
+
exists, Og does not recreate it. It is useful to get Og to
|
29
|
+
automatically create the schema for your application on setup,
|
30
|
+
or when you add a new managed class to your application. On
|
31
|
+
the downside, it inflicts a longer startup time on the
|
32
|
+
application, so it is better to set this to false in
|
33
|
+
production / live environments.
|
34
|
+
|
35
|
+
This option is by default true to facilitate easy development.
|
data/doc/tutorial.txt
ADDED
@@ -0,0 +1,595 @@
|
|
1
|
+
= Og Tutorial
|
2
|
+
|
3
|
+
=== Introduction
|
4
|
+
|
5
|
+
Ruby is a wonderful object oriented language featuring a well
|
6
|
+
designed syntax and advanced constructs to bring the joy back
|
7
|
+
to programming. Creating the object model to describe your
|
8
|
+
problem domain is easy, but making this model persistent is
|
9
|
+
another story: you have to deal with relational databases
|
10
|
+
and the SQL language.
|
11
|
+
|
12
|
+
RDBMS systems are a proven and robust technology for storing
|
13
|
+
and querying data, but after experiencing the wonders of Ruby,
|
14
|
+
it is hard not to wish for a better way to integrate the OOP
|
15
|
+
and Relational paradigms.
|
16
|
+
|
17
|
+
Og makes your dream come true! Og stands for ObjectGraph and
|
18
|
+
provides a transparent way to make your objects persistent
|
19
|
+
while leveraging the full querying power of an RDBMS system.
|
20
|
+
In fact, Og is designed to use an RDBMS system like MySQL or
|
21
|
+
PostgreSQL to implement the actual data store where the
|
22
|
+
objects are serialized.
|
23
|
+
|
24
|
+
But, enough with the techno-babble, let's walk through a simple
|
25
|
+
example to give you a better idea of what Og can do.
|
26
|
+
|
27
|
+
|
28
|
+
=== Installing Og
|
29
|
+
|
30
|
+
The best way to install Og is through RubyGems. For example:
|
31
|
+
|
32
|
+
gem install og
|
33
|
+
|
34
|
+
In order to use Og with a specific RDBMS, you have to install
|
35
|
+
the corresponding Ruby binding. A list of supported RDBMS's
|
36
|
+
and information about the Ruby bindings can be found in the
|
37
|
+
README file.
|
38
|
+
|
39
|
+
Alternatively, you can install a .tar.gz or .zip distribution.
|
40
|
+
You can find these at the following URL:
|
41
|
+
|
42
|
+
http://www.rubyforge.com/projects/nitro
|
43
|
+
|
44
|
+
|
45
|
+
=== A Basic Blog Model
|
46
|
+
|
47
|
+
Blogs are in vogue. It seems that almost everyone is running a blog, and
|
48
|
+
many try to code one from scratch. We'll review the steps necessary
|
49
|
+
to generate the persistence model for a blog application using Og.
|
50
|
+
|
51
|
+
Let's start by designing the objects we'll use. Our simple Blog
|
52
|
+
will use these three objects:
|
53
|
+
|
54
|
+
# Blog category
|
55
|
+
|
56
|
+
class Category
|
57
|
+
attr_accessor :name
|
58
|
+
end
|
59
|
+
|
60
|
+
# Blog posting
|
61
|
+
|
62
|
+
class Post
|
63
|
+
attr_accessor :title
|
64
|
+
attr_accessor :body
|
65
|
+
attr_accessor :author
|
66
|
+
end
|
67
|
+
|
68
|
+
# Blog comment
|
69
|
+
|
70
|
+
class Comment
|
71
|
+
attr_accessor :title
|
72
|
+
attr_accessor :body
|
73
|
+
attr_accessor :author
|
74
|
+
end
|
75
|
+
|
76
|
+
As you can see, this is pure Ruby code. One of the features of
|
77
|
+
Ruby is dynamic typing. When defining the attributes of our
|
78
|
+
objects, we don't declare the actual type. However, in order to
|
79
|
+
persist the model in SQL, we need to provide some hints to Og.
|
80
|
+
|
81
|
+
Og provides a replacement to the attr* family of methods to
|
82
|
+
facilitate attaching metadata to the object's attributes. An
|
83
|
+
attribute that contains metadata is called a property. For
|
84
|
+
each attr* method, there is a corresponding prop* method. That is,
|
85
|
+
|
86
|
+
attr => prop
|
87
|
+
attr_accessor => prop_accessor
|
88
|
+
attr_reader => prop_reader
|
89
|
+
attr_writer => prop_writer
|
90
|
+
|
91
|
+
Here are the class definitions using the property mechanism:
|
92
|
+
|
93
|
+
require 'og'
|
94
|
+
|
95
|
+
class Category
|
96
|
+
prop_accessor :name, String
|
97
|
+
end
|
98
|
+
|
99
|
+
class Post
|
100
|
+
prop_accessor :title, String
|
101
|
+
prop_accessor :body, String
|
102
|
+
prop_accessor :author, String
|
103
|
+
prop_accessor :create_time, Time
|
104
|
+
prop_accessor :hits, Fixnum
|
105
|
+
end
|
106
|
+
|
107
|
+
class Comment
|
108
|
+
prop_accessor :title, String
|
109
|
+
prop_accessor :body, String
|
110
|
+
prop_accessor :author, String
|
111
|
+
prop_accessor :create_time, Time
|
112
|
+
end
|
113
|
+
|
114
|
+
Notice that the prop_accessor works similar to Ruby's attr_accessor.
|
115
|
+
Here are some examples:
|
116
|
+
|
117
|
+
prop :title, true, String
|
118
|
+
prop_reader :title, :body, :author, String
|
119
|
+
|
120
|
+
To make the definitions look even cleaner, Og provides the property alias:
|
121
|
+
|
122
|
+
class Category
|
123
|
+
property :name, String
|
124
|
+
end
|
125
|
+
|
126
|
+
class Post
|
127
|
+
property :title, String
|
128
|
+
property :body, String
|
129
|
+
property :author, String
|
130
|
+
property :create_time, Time
|
131
|
+
property :hits, Fixnum
|
132
|
+
end
|
133
|
+
|
134
|
+
class Comment
|
135
|
+
property :title, String
|
136
|
+
property :body, String
|
137
|
+
property :author, String
|
138
|
+
property :create_time, Time
|
139
|
+
end
|
140
|
+
|
141
|
+
This is most of the information that Og needs to manage these objects. Before we continue,
|
142
|
+
we need to setup the actual RDBMS data store used by Og. Currently, Og has built-in adapters
|
143
|
+
for PostgreSQL, MySQL, SQLite3, and Oracle. For this example, we'll use the PostgreSQL adapter,
|
144
|
+
so add this code after the class definitions.
|
145
|
+
|
146
|
+
db = Og::Database.new(
|
147
|
+
:database => 'test',
|
148
|
+
:adapter => 'psql',
|
149
|
+
:user => 'postgres',
|
150
|
+
:password => 'navelrulez'
|
151
|
+
)
|
152
|
+
|
153
|
+
Now you are ready to save your first object into Postgres. Add the following code:
|
154
|
+
|
155
|
+
# create the object
|
156
|
+
p = Post.new
|
157
|
+
p.title = 'Hello'
|
158
|
+
p.body = 'World'
|
159
|
+
p.author = 'tml'
|
160
|
+
|
161
|
+
# save the object in the database
|
162
|
+
p.save
|
163
|
+
|
164
|
+
That's it! Og works behind the scenes doing all the work for you.
|
165
|
+
This simple command, p.save, does the following:
|
166
|
+
|
167
|
+
1. Creates the database 'test' if it doesn't exist.
|
168
|
+
2. Creates a table to store Post objects if it doesn't exist.
|
169
|
+
The table's columns map to the object properties.
|
170
|
+
3. Creates SQL indices.
|
171
|
+
4. Creates any needed sequences.
|
172
|
+
5. Serializes the object into the table.
|
173
|
+
|
174
|
+
Issue the following SQL to see the result:
|
175
|
+
|
176
|
+
SELECT * FROM og_post
|
177
|
+
|
178
|
+
This is nice, but where does the #save method come from?
|
179
|
+
Og uses Ruby's advanced introspection features to automatically
|
180
|
+
'enchant' class that define properties. An enchanted class
|
181
|
+
provides several methods that will be discussed in the following
|
182
|
+
text. These enchanted classes are called *managed* classes.
|
183
|
+
|
184
|
+
Before going on, let's look at another Og macro that eases object creation:
|
185
|
+
|
186
|
+
p = Post.create
|
187
|
+
|
188
|
+
Create automatically calls the save method. Here is another way to save the object:
|
189
|
+
|
190
|
+
db << p
|
191
|
+
|
192
|
+
OR
|
193
|
+
|
194
|
+
db.save(p)
|
195
|
+
|
196
|
+
|
197
|
+
Let's create a Category object.
|
198
|
+
|
199
|
+
cat = Category.new
|
200
|
+
cat.name = 'Programming'
|
201
|
+
cat.save
|
202
|
+
|
203
|
+
If you investigate the generated og_category table, you will
|
204
|
+
see an 'oid' column which serves as the primary key. This
|
205
|
+
column is added automatically by Og. You can use the oid
|
206
|
+
values to lookup objects:
|
207
|
+
|
208
|
+
cat = Category[1] # loads the category object with oid = 1
|
209
|
+
|
210
|
+
OR
|
211
|
+
|
212
|
+
cat = db.load(1, Category)
|
213
|
+
|
214
|
+
As a convenience, Og allows you to lookup the category
|
215
|
+
using the special property 'name':
|
216
|
+
|
217
|
+
cat = Category['Programming']
|
218
|
+
|
219
|
+
You can lookup objects by name only if the name property is
|
220
|
+
defined.
|
221
|
+
|
222
|
+
If you want to view Og's SQL, you can enable debug mode by setting this global
|
223
|
+
debug (DBG) variable:
|
224
|
+
|
225
|
+
$DBG = true
|
226
|
+
|
227
|
+
|
228
|
+
=== Customizing the Schema and Defining Relations
|
229
|
+
|
230
|
+
Og makes our blog model persistent through a simple interface. The next step is to
|
231
|
+
refine the schema and define relations between the objects:
|
232
|
+
|
233
|
+
class Post; end
|
234
|
+
class Comment; end
|
235
|
+
|
236
|
+
class Category
|
237
|
+
property :name, String
|
238
|
+
|
239
|
+
many_to_many :posts, Post
|
240
|
+
|
241
|
+
def initialize(title = nil)
|
242
|
+
@title = title
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
class Post
|
247
|
+
property :title, String, :sql => 'VARCHAR2(32) NOT NULL'
|
248
|
+
property :body, String
|
249
|
+
property :author, String
|
250
|
+
property :create_time, Time
|
251
|
+
property :hits, Fixnum, :sql_index => true
|
252
|
+
|
253
|
+
has_many :comments, Comment
|
254
|
+
|
255
|
+
def initialize(title = nil, body = nil, author = nil)
|
256
|
+
@title, @body, @author = title, body, author
|
257
|
+
@create_time = Time.now
|
258
|
+
@hits = 0
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
class Comment
|
263
|
+
property :title, String, :sql => 'VARCHAR2(32) NOT NULL'
|
264
|
+
property :body, String
|
265
|
+
property :author, String
|
266
|
+
property :create_time, Time
|
267
|
+
|
268
|
+
belongs_to :post, Post
|
269
|
+
|
270
|
+
def initialize(title = nil, body = nil, author = nil)
|
271
|
+
@title, @body, @author = title, body, author
|
272
|
+
@create_time = Time.now
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
Observe the :sql property option is used to refine
|
277
|
+
the generated column type for the title property of Post,
|
278
|
+
and how the :sql_index option is used to add an index to
|
279
|
+
the generated table.
|
280
|
+
|
281
|
+
Notice that the initialize methods provide default
|
282
|
+
values to all parameters. This is required for all managed objects.
|
283
|
+
|
284
|
+
Observe the many_to_many, has_many, and belongs_to macros.
|
285
|
+
Og uses these macros to define the relations between
|
286
|
+
standard Objects. In essence, Og defines a domain specific
|
287
|
+
mini language. The following kinds of relations are
|
288
|
+
supported:
|
289
|
+
|
290
|
+
* has_one: has one object of the given type.
|
291
|
+
|
292
|
+
* has_many: has many objects of the given type.
|
293
|
+
|
294
|
+
* belongs_to: belongs_to an object of the given type.
|
295
|
+
|
296
|
+
* many_to_many: defines a many-to-many relation. The corresponding
|
297
|
+
rows in the database are linked through a join table.
|
298
|
+
|
299
|
+
* refers_to: refers to another object.
|
300
|
+
|
301
|
+
These macros generate the constructs needed to efficiently implement
|
302
|
+
the corresponding relations. For example, the belongs_to macro generates
|
303
|
+
the property that links to the parent. The many_to_many relation generates
|
304
|
+
the join table that links the participating classes.
|
305
|
+
|
306
|
+
Note that we have to use forward definitions of Post and Comment to satisfy
|
307
|
+
Ruby's parser. Workarounds will be provided in a future version.
|
308
|
+
|
309
|
+
After defining these relations, using and querying the object model is easy:
|
310
|
+
|
311
|
+
cat = Category.create('Programming')
|
312
|
+
|
313
|
+
cat.add_post { |p|
|
314
|
+
p.title = 'Title'
|
315
|
+
p.body = 'Body
|
316
|
+
}
|
317
|
+
|
318
|
+
cat.add_post { |p|
|
319
|
+
p.title = 'Another'
|
320
|
+
p.body = 'Hello'
|
321
|
+
}
|
322
|
+
|
323
|
+
cat.posts
|
324
|
+
=> [Post(Title), Post(Another)]
|
325
|
+
|
326
|
+
cat.posts[0].title
|
327
|
+
=> Title
|
328
|
+
|
329
|
+
cat.posts.size
|
330
|
+
=> 2
|
331
|
+
|
332
|
+
p = Post[1]
|
333
|
+
p.title
|
334
|
+
=> Title
|
335
|
+
|
336
|
+
p.categories[0].title
|
337
|
+
=> 'Programming'
|
338
|
+
|
339
|
+
c = Comment.new('hello', 'world', 'tml')
|
340
|
+
c.post = p
|
341
|
+
c.save
|
342
|
+
|
343
|
+
p.comments.size
|
344
|
+
=> 1
|
345
|
+
|
346
|
+
p.add_comment { |c|
|
347
|
+
c.title = 'Hi there'
|
348
|
+
}
|
349
|
+
|
350
|
+
p.comments[1].title
|
351
|
+
=> 'Hi there'
|
352
|
+
|
353
|
+
com = Comment.new('Hi there')
|
354
|
+
p.add_comment(com)
|
355
|
+
|
356
|
+
All the methods used in the above examples are generated automatically.
|
357
|
+
These methods transparently modify the underlying SQL schema using efficient queries.
|
358
|
+
|
359
|
+
Og provides full access to all features of the underlying RDBMS. Look at the following:
|
360
|
+
|
361
|
+
post = Post.select("title='Title' and body='Body'")
|
362
|
+
post.size
|
363
|
+
=> 1
|
364
|
+
post.hits
|
365
|
+
=> 0
|
366
|
+
|
367
|
+
Updating existing objects is easy too:
|
368
|
+
|
369
|
+
p = Post[1]
|
370
|
+
p.title = 'Changed'
|
371
|
+
p.save
|
372
|
+
|
373
|
+
p = Post[1]
|
374
|
+
p.title
|
375
|
+
=> 'Changed'
|
376
|
+
|
377
|
+
You can also update specific properties, for example:
|
378
|
+
|
379
|
+
p = Post[1]
|
380
|
+
p.update_properties "body='Hello world'"
|
381
|
+
|
382
|
+
p = Post[1]
|
383
|
+
p.body
|
384
|
+
=> 'Hello world'
|
385
|
+
|
386
|
+
If you don't like a particular comment, you can easily delete it by doing the following:
|
387
|
+
|
388
|
+
Comment.delete(comment)
|
389
|
+
|
390
|
+
OR
|
391
|
+
|
392
|
+
comment.delete!
|
393
|
+
|
394
|
+
OR
|
395
|
+
|
396
|
+
db.delete(comment)
|
397
|
+
|
398
|
+
To delete all comments for a posting, enter the following:
|
399
|
+
|
400
|
+
p.delete_all_comments
|
401
|
+
|
402
|
+
When deleting an object that participates in relations, Og tries
|
403
|
+
to delete all objects that belong to this object (ie, cascade deletes).
|
404
|
+
|
405
|
+
All the generated methods take more parameters to customize their
|
406
|
+
behaviour to suit your needs.
|
407
|
+
|
408
|
+
|
409
|
+
=== Defining Callbacks
|
410
|
+
|
411
|
+
Og provides a detailed callback facility allowing you to hook
|
412
|
+
into a managed object's Lifecycle. This is a very useful
|
413
|
+
feature that can improve your code considerably. To implement
|
414
|
+
a callback, you have to define one or more of the following methods
|
415
|
+
in your class:
|
416
|
+
|
417
|
+
* og_pre_insert
|
418
|
+
* og_post_insert
|
419
|
+
* og_pre_update
|
420
|
+
* og_post_update
|
421
|
+
* og_pre_insert_update
|
422
|
+
* og_post_insert_update
|
423
|
+
* self.og_pre_delete
|
424
|
+
|
425
|
+
For example, the following code defines a callback for the Post class.
|
426
|
+
|
427
|
+
class Post
|
428
|
+
...
|
429
|
+
|
430
|
+
def og_post_insert(conn)
|
431
|
+
puts 'Hey, a new post was just posted!'
|
432
|
+
end
|
433
|
+
end
|
434
|
+
|
435
|
+
When post.save is called, you'll get this alert:
|
436
|
+
|
437
|
+
p = Post.create('Hello')
|
438
|
+
=> console: Hey, a new post was just posted!
|
439
|
+
|
440
|
+
|
441
|
+
=== Using OOP techniques
|
442
|
+
|
443
|
+
Og's managed objects are standard Ruby objects, so we can use class inheritance
|
444
|
+
and module inclusion to minimize the code we have to write. Here's how we can
|
445
|
+
improve the blog schema:
|
446
|
+
|
447
|
+
class Category
|
448
|
+
property :name, String
|
449
|
+
many_to_many :posts, Post
|
450
|
+
|
451
|
+
def initialize(title = nil)
|
452
|
+
@title = title
|
453
|
+
end
|
454
|
+
end
|
455
|
+
|
456
|
+
class Common
|
457
|
+
property :title, String, :sql => 'VARCHAR2(32) NOT NULL'
|
458
|
+
property :body, String
|
459
|
+
property :author, String
|
460
|
+
property :create_time, Time
|
461
|
+
|
462
|
+
def initialize(title = nil, body = nil, author = nil)
|
463
|
+
@title, @body, @author = title, body, author
|
464
|
+
@create_time = Time.now
|
465
|
+
end
|
466
|
+
end
|
467
|
+
|
468
|
+
class Post < Common
|
469
|
+
property :hits, Fixnum, :sql_index => true
|
470
|
+
has_many :comments, Comment
|
471
|
+
|
472
|
+
def initialize(title = nil, body = nil, author = nil)
|
473
|
+
super
|
474
|
+
@hits = 0
|
475
|
+
end
|
476
|
+
end
|
477
|
+
|
478
|
+
class Comment < Common
|
479
|
+
belongs_to :post, Post
|
480
|
+
end
|
481
|
+
|
482
|
+
In essence, this feature allows you to create SQL tables using inheritance,
|
483
|
+
saving you lots of time when using objects with similar properties. It's also
|
484
|
+
less error prone.
|
485
|
+
|
486
|
+
|
487
|
+
=== Defining Validation Rules
|
488
|
+
|
489
|
+
When managing large amounts of data, enforcing data integrity is
|
490
|
+
important. Og provides another domain specific mini language that allows
|
491
|
+
you to define validation rules in a simple manner. In the following code,
|
492
|
+
the blog schema is enriched with hints that allows Og to automatically generate
|
493
|
+
validation code:
|
494
|
+
|
495
|
+
class Common
|
496
|
+
property :title, String, :sql => 'NOT NULL VARCHAR(32)'
|
497
|
+
property :body, String
|
498
|
+
property :author, String
|
499
|
+
property :create_time, String
|
500
|
+
|
501
|
+
validate_value :title
|
502
|
+
validate_length :body, :range => 2..100, :msg_long => 'argh'
|
503
|
+
validate_format :author, :format => /[a-z]/, :msg => 'wrong format'
|
504
|
+
|
505
|
+
def initialize(title = nil, body = nil, author = nil)
|
506
|
+
@title, @body, @author = title, body, author
|
507
|
+
@create_time = Time.now
|
508
|
+
end
|
509
|
+
end
|
510
|
+
|
511
|
+
This code demonstrates some validations facilities. Using the validate_value
|
512
|
+
macro, we enforce that the 'title' property will have a value. Using the
|
513
|
+
validate_length macro, we enforce the minimum and maximum lengths for the
|
514
|
+
'body' property. Using the validate_format macro, we enforce a required
|
515
|
+
format for values assigned to the 'author' field.
|
516
|
+
|
517
|
+
Let's see this validation in practice:
|
518
|
+
|
519
|
+
c = Comment.new
|
520
|
+
c.valid?
|
521
|
+
=> false
|
522
|
+
c.errors.count
|
523
|
+
=> 3
|
524
|
+
|
525
|
+
c.title = 'Hello'
|
526
|
+
c.valid?
|
527
|
+
c.errors.count
|
528
|
+
=> 2
|
529
|
+
|
530
|
+
The errors array contains a list of Error objects that point to the offending
|
531
|
+
field and contain a descriptive message.
|
532
|
+
|
533
|
+
With Og, you can customize almost everything! More information can be found in the source code
|
534
|
+
(lib/glue/validation.rb). To whet your appetite, here is a list of predefined validation macros:
|
535
|
+
|
536
|
+
* validate_value
|
537
|
+
* validate_format
|
538
|
+
* validate_length
|
539
|
+
* validate_inclusion
|
540
|
+
* validate_confirmation
|
541
|
+
|
542
|
+
|
543
|
+
=== TypeMacros
|
544
|
+
|
545
|
+
If you look at the common class definition, you will notice that the :sql
|
546
|
+
option looks kind of ugly:
|
547
|
+
|
548
|
+
:sql => 'VARCHAR2(32) NOT NULL'
|
549
|
+
|
550
|
+
When building larger object models, this issue comes up frequently. Og
|
551
|
+
provides a elegant solution in the form of type macros:
|
552
|
+
|
553
|
+
def VarChar(size)
|
554
|
+
return String, :sql => "VARCHAR2(#{size}) NOT NULL"
|
555
|
+
end
|
556
|
+
|
557
|
+
property :title, VarChar(30)
|
558
|
+
|
559
|
+
|
560
|
+
=== Switching To Another Database
|
561
|
+
|
562
|
+
While Postgres is a great database, let's assume that the client wants to switch to
|
563
|
+
MySQL at the last minute. Don't worry, Og can easily accomodate this by simply changing
|
564
|
+
the db reference in the configuration file to look like this and then re-running the example:
|
565
|
+
|
566
|
+
db = Og::Database.new(
|
567
|
+
:database => 'test',
|
568
|
+
:adapter => 'mysql',
|
569
|
+
:user => 'postgres',
|
570
|
+
:password => 'navelrulez'
|
571
|
+
)
|
572
|
+
|
573
|
+
A new MySQL database is automatically created along with all tables, indices, etc. You get all
|
574
|
+
this with changing only one line of code!
|
575
|
+
|
576
|
+
|
577
|
+
=== Is There More?
|
578
|
+
|
579
|
+
You betcha! You can to find more about Og by reading the available RDoc documentation and browsing the examples.
|
580
|
+
|
581
|
+
For any questions regarding Og, feel free to ask on the ruby-talk
|
582
|
+
mailing list (which is mirrored to comp.lang.ruby) or contact
|
583
|
+
mailto:gm@navel.gr.
|
584
|
+
|
585
|
+
A Nitro specific mailing list is also available. You can post questions about
|
586
|
+
Og to this list. Please subscribe to nitro-general@rubyforge.com. The homepage
|
587
|
+
for this list is available here:
|
588
|
+
|
589
|
+
http://rubyforge.org/mailman/listinfo/nitro-general
|
590
|
+
|
591
|
+
Note that Og is still under heavy development, so new features are being added frequently.
|
592
|
+
Be sure to check back for updates.
|
593
|
+
|
594
|
+
|
595
|
+
|