og 0.12.0 → 0.13.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. data/CHANGELOG +27 -0
  2. data/INSTALL +56 -0
  3. data/{README.og → README} +3 -3
  4. data/Rakefile +7 -73
  5. data/benchmark/bench.rb +75 -0
  6. data/benchmark/sqlite-no-prepare.1.txt +13 -0
  7. data/benchmark/sqlite-no-prepare.2.txt +13 -0
  8. data/benchmark/sqlite-prepare.1.txt +13 -0
  9. data/benchmark/sqlite-prepare.2.txt +13 -0
  10. data/doc/AUTHORS +0 -9
  11. data/{RELEASES.og → doc/RELEASES} +15 -0
  12. data/doc/config.txt +35 -0
  13. data/doc/tutorial.txt +595 -0
  14. data/examples/{og/README → README} +1 -1
  15. data/examples/{og/mock_example.rb → mock_example.rb} +1 -1
  16. data/examples/{og/mysql_to_psql.rb → mysql_to_psql.rb} +1 -1
  17. data/examples/{og/run.rb → run.rb} +1 -1
  18. data/install.rb +8 -5
  19. data/lib/og.rb +13 -8
  20. data/lib/og/adapter.rb +1 -1
  21. data/lib/og/adapters/filesys.rb +1 -1
  22. data/lib/og/adapters/mysql.rb +4 -3
  23. data/lib/og/adapters/oracle.rb +1 -1
  24. data/lib/og/adapters/psql.rb +4 -3
  25. data/lib/og/adapters/sqlite.rb +1 -1
  26. data/lib/og/backend.rb +1 -1
  27. data/lib/og/connection.rb +1 -1
  28. data/lib/og/database.rb +1 -1
  29. data/lib/og/meta.rb +13 -2
  30. data/lib/og/observer.rb +1 -1
  31. data/lib/og/typemacros.rb +1 -1
  32. data/lib/og/validation.rb +81 -0
  33. data/test/og/tc_validation.rb +89 -0
  34. metadata +33 -65
  35. data/ChangeLog +0 -1549
  36. data/lib/glue.rb +0 -55
  37. data/lib/glue/array.rb +0 -61
  38. data/lib/glue/attribute.rb +0 -83
  39. data/lib/glue/cache.rb +0 -138
  40. data/lib/glue/flexob.rb +0 -12
  41. data/lib/glue/hash.rb +0 -122
  42. data/lib/glue/inflector.rb +0 -91
  43. data/lib/glue/logger.rb +0 -147
  44. data/lib/glue/misc.rb +0 -14
  45. data/lib/glue/mixins.rb +0 -36
  46. data/lib/glue/number.rb +0 -24
  47. data/lib/glue/object.rb +0 -32
  48. data/lib/glue/pool.rb +0 -60
  49. data/lib/glue/property.rb +0 -408
  50. data/lib/glue/string.rb +0 -162
  51. data/lib/glue/time.rb +0 -85
  52. data/lib/glue/validation.rb +0 -394
  53. data/vendor/extensions/_base.rb +0 -153
  54. data/vendor/extensions/_template.rb +0 -36
  55. data/vendor/extensions/all.rb +0 -21
  56. data/vendor/extensions/array.rb +0 -68
  57. data/vendor/extensions/binding.rb +0 -224
  58. data/vendor/extensions/class.rb +0 -50
  59. data/vendor/extensions/continuation.rb +0 -71
  60. data/vendor/extensions/enumerable.rb +0 -250
  61. data/vendor/extensions/hash.rb +0 -23
  62. data/vendor/extensions/io.rb +0 -58
  63. data/vendor/extensions/kernel.rb +0 -42
  64. data/vendor/extensions/module.rb +0 -114
  65. data/vendor/extensions/numeric.rb +0 -230
  66. data/vendor/extensions/object.rb +0 -164
  67. data/vendor/extensions/ostruct.rb +0 -41
  68. data/vendor/extensions/string.rb +0 -316
  69. data/vendor/extensions/symbol.rb +0 -28
@@ -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
+
@@ -1,6 +1,6 @@
1
- = Og 0.12.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://www.rubyforge.com/projects/nitro).
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/og_config.txt', 'doc/og_tutorial.txt', 'lib/**/*.rb')
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.0'
46
+ s.required_ruby_version = '>= 1.8.1'
60
47
 
61
48
  s.files = FileList[
62
- 'README.og', 'RELEASES.og', 'doc/LICENSE', 'doc/AUTHORS', 'Rakefile', 'ChangeLog*',
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.og', 'RELEASES.og', 'doc/LICENSE', 'doc/AUTHORS'].to_a
73
- s.rdoc_options << '--main' << 'README.og' << '--title' << 'Og Documentation'
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://www.rubyforge.com/projects/nitro'
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
@@ -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
@@ -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
@@ -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.
@@ -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
+