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.
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
+