og 0.5.0 → 0.6.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/README.og CHANGED
@@ -1,4 +1,4 @@
1
- = Og 0.5.0
1
+ = Og 0.6.0
2
2
 
3
3
  Og (ObjectGraph) is an efficient, yet simple object-relational mapping
4
4
  library. Og provides transparent serialization of object graphs to a RDBMS
@@ -28,6 +28,7 @@ The library provides the following features:
28
28
  + Absolutely no configuration files.
29
29
  + Multiple backends (PostgreSQL, MySQL).
30
30
  + ActiveRecord-style meta language and db aware methods.
31
+ + Automatially generates join-tables for many_to_many relations.
31
32
  + Deserialize to Ruby Objects or ResultSets.
32
33
  + Deserialize sql join queries to Ruby Objects.
33
34
  + Serialize arbitrary ruby object graphs through YAML.
@@ -1,3 +1,13 @@
1
+ == Version 0.6 was released on 13/12/2004.
2
+
3
+ This is a preview release, the api for the new features is not
4
+ finalized. This early release gives other developers to offer suggestions
5
+ on the final form of those features.
6
+
7
+ Most notable additions:
8
+
9
+ * Og many_to_many relations with auto generation of the join table.
10
+ * Og has_one relation.
1
11
 
2
12
  == Version 0.5.0 was released on 21/11/2004.
3
13
 
@@ -6,7 +6,7 @@
6
6
  # * George Moschovitis <gm@navel.gr>
7
7
  #
8
8
  # (c) 2004 Navel, all rights reserved.
9
- # $Id: run.rb 167 2004-11-23 14:03:10Z gmosx $
9
+ # $Id: run.rb 185 2004-12-10 13:29:09Z gmosx $
10
10
 
11
11
  $:.unshift "../../lib"
12
12
 
@@ -55,6 +55,7 @@ class User
55
55
  end
56
56
  end
57
57
 
58
+
58
59
  # = A parent class
59
60
  #
60
61
  class Article
@@ -95,6 +96,21 @@ class Article
95
96
  end
96
97
  end
97
98
 
99
+ # = A parent class
100
+ #
101
+ class Category
102
+ prop_accessor :title, String
103
+ prop_accessor :body, String
104
+
105
+ # define a 'many to many' relation.
106
+ many_to_many Article
107
+
108
+ def initialize(title = nil)
109
+ @title = title
110
+ end
111
+ end
112
+
113
+
98
114
  # = Article comment
99
115
  #
100
116
  class ArticleComment < Comment
@@ -127,7 +143,6 @@ end
127
143
  $log = Logger.new(STDERR);
128
144
 
129
145
  # Og configuration.
130
-
131
146
  config = {
132
147
  :address => "localhost",
133
148
  :database => "test",
@@ -249,3 +264,27 @@ part.save!
249
264
 
250
265
  article.parts.each { |pa| puts pa }
251
266
 
267
+ puts "\n\n"
268
+ puts '---'
269
+
270
+ c1 = Category.new("Category1").save!
271
+ c2 = Category.new("Category2").save!
272
+
273
+ article.add_category(c1)
274
+ article.add_category(c2)
275
+
276
+ puts '---'
277
+
278
+ article.categories.each { |c| puts c.title }
279
+
280
+ puts '---'
281
+
282
+ c2.articles.each { |a| puts a.title }
283
+
284
+ article.del_category(c1)
285
+
286
+ puts '---'
287
+
288
+ article.categories.each { |c| puts c.title }
289
+
290
+
@@ -6,7 +6,7 @@
6
6
  # * George Moschovitis <gm@navel.gr>
7
7
  #
8
8
  # (c) 2004 Navel, all rights reserved.
9
- # $Id$
9
+ # $Id: glue.rb 175 2004-11-26 16:11:27Z gmosx $
10
10
 
11
11
  require "English"
12
12
  require "pp"
@@ -5,7 +5,7 @@
5
5
  # * Elias Karakoulakis <ekarak@ktismata.com>
6
6
  #
7
7
  # (c) 2004 Navel, all rights reserved.
8
- # $Id: property.rb 167 2004-11-23 14:03:10Z gmosx $
8
+ # $Id: property.rb 185 2004-12-10 13:29:09Z gmosx $
9
9
 
10
10
  require "glue/array"
11
11
  require "glue/hash"
@@ -48,6 +48,10 @@ class Property
48
48
  def ==(other)
49
49
  return @symbol == other.symbol
50
50
  end
51
+
52
+ def to_s
53
+ return name
54
+ end
51
55
  end
52
56
 
53
57
  end # module
@@ -236,6 +240,8 @@ class Module
236
240
  }
237
241
  end
238
242
 
243
+ # gmosx: __force_xxx reuses xxx= to allow for easier
244
+ # overrides.
239
245
  if writer
240
246
  module_eval %{
241
247
  def #{s}=(val)
@@ -243,7 +249,7 @@ class Module
243
249
  end
244
250
 
245
251
  def __force_#{s}(val)
246
- @#{s} = } + case klass.name
252
+ self.#{s}=(} + case klass.name
247
253
  when Fixnum.name
248
254
  "val.to_i()"
249
255
  when String.name
@@ -256,7 +262,7 @@ class Module
256
262
  "val.to_i() > 0"
257
263
  else
258
264
  "val"
259
- end + %{
265
+ end + %{)
260
266
  end
261
267
  }
262
268
  end
data/lib/og.rb CHANGED
@@ -2,7 +2,7 @@
2
2
  # * George Moschovitis <gm@navel.gr>
3
3
  #
4
4
  # (c) 2004 Navel, all rights reserved.
5
- # $Id: og.rb 167 2004-11-23 14:03:10Z gmosx $
5
+ # $Id: og.rb 185 2004-12-10 13:29:09Z gmosx $
6
6
 
7
7
  require "glue/property"
8
8
  require "glue/array"
@@ -10,6 +10,24 @@ require "glue/hash"
10
10
  require "glue/time"
11
11
  require "glue/pool"
12
12
 
13
+ # If true, only allow reading from the database. Usefull
14
+ # for maintainance.
15
+ #
16
+ $og_read_only_mode = false
17
+
18
+ # If true, the library automatically 'enchants' managed classes.
19
+ # In enchant mode, special db aware methods are added to
20
+ # managed classes and instances.
21
+ #
22
+ $og_enchant_managed_classes = true
23
+
24
+ # If true, use Ruby's advanced introspection capabilities to
25
+ # automatically manage classes tha define properties.
26
+ $og_auto_manage_classes = true
27
+
28
+ # If true, automatically include the Og meta-language into Module.
29
+ $og_include_meta_language = true
30
+
13
31
  require "og/meta"
14
32
 
15
33
  # = Og
@@ -93,21 +111,6 @@ require "og/meta"
93
111
  #
94
112
  module Og
95
113
 
96
- # If true, only allow reading from the database. Usefull
97
- # for maintainance.
98
- #
99
- $og_read_only_mode = false
100
-
101
- # If true, the library automatically 'enchants' managed classes.
102
- # In enchant mode, special db aware methods are added to
103
- # managed classes and instances.
104
- #
105
- $og_enchant_managed_classes = true
106
-
107
- # If true, use Ruby's advanced introspection capabilities to
108
- # automatically manage classes tha define properties.
109
- $og_auto_manage_classes = true
110
-
111
114
  # = Unmanageable
112
115
  #
113
116
  # Marker module. If included this in a class, the Og automanager
@@ -328,7 +331,11 @@ class Database
328
331
  def self.all(extra_sql = nil)
329
332
  $og.load_all(#{klass}, extra_sql)
330
333
  end
331
-
334
+
335
+ def self.count(sql = "SELECT COUNT(*) FROM #{klass::DBTABLE}")
336
+ $og.count(sql, #{klass})
337
+ end
338
+
332
339
  def self.select(sql)
333
340
  $og.select(sql, #{klass})
334
341
  end
@@ -343,6 +350,7 @@ class Database
343
350
 
344
351
  def save
345
352
  $og << self
353
+ return self
346
354
  end
347
355
  alias_method :save!, :save
348
356
 
@@ -2,7 +2,7 @@
2
2
  # * George Moschovitis <gm@navel.gr>
3
3
  #
4
4
  # (c) 2004 Navel, all rights reserved.
5
- # $Id: backend.rb 155 2004-11-13 20:32:12Z gmosx $
5
+ # $Id: backend.rb 185 2004-12-10 13:29:09Z gmosx $
6
6
 
7
7
  require "yaml"
8
8
 
@@ -16,15 +16,27 @@ module Og
16
16
  #
17
17
  module Utils
18
18
 
19
- # The name of the SQL table where objects of this class are stored.
19
+ # Encode the name of the klass as an sql safe string.
20
20
  # The Module separators are replaced with _ and NOT stripped out so
21
21
  # that we can convert back to the original notation if needed.
22
22
  # The leading module if available is removed.
23
23
  #
24
+ def self.encode(klass)
25
+ "#{klass.name.gsub(/^.*::/, "")}".gsub(/::/, "_").downcase
26
+ end
27
+
28
+ # The name of the SQL table where objects of this class are stored.
29
+ #
24
30
  def self.table(klass)
25
- return "_#{klass.name.gsub(/^.*::/, "")}".gsub(/::/, "_").downcase
31
+ "_#{encode(klass)}"
26
32
  end
27
33
 
34
+ # The name of the join table for the two given classes.
35
+ #
36
+ def self.join_table(klass1, klass2)
37
+ "_j_#{Og::Utils.encode(klass1)}_#{Og::Utils.encode(klass2)}"
38
+ end
39
+
28
40
  # Returns the props that will be included in the insert query.
29
41
  # For some backends the oid should be stripped.
30
42
  #
@@ -3,7 +3,7 @@
3
3
  # * Elias Athanasopoulos <elathan@navel.gr>
4
4
  #
5
5
  # (c) 2004 Navel, all rights reserved.
6
- # $Id: mysql.rb 159 2004-11-18 10:18:30Z gmosx $
6
+ # $Id: mysql.rb 185 2004-12-10 13:29:09Z gmosx $
7
7
 
8
8
  require "mysql"
9
9
 
@@ -302,14 +302,41 @@ class MysqlBackend < Og::Backend
302
302
 
303
303
  # Create indices
304
304
 
305
- if klass.__meta
306
- for data in klass.__meta[:sql_index]
305
+ if klass.__meta and indices = klass.__meta[:sql_index]
306
+ for data in indices
307
307
  idx, options = *data
308
308
  idx = idx.to_s
309
309
  pre_sql, post_sql = options[:pre], options[:post]
310
310
  idxname = idx.gsub(/ /, "").gsub(/,/, "_").gsub(/\(.*\)/, "")
311
311
  exec "CREATE #{pre_sql} INDEX #{klass::DBTABLE}_#{idxname}_idx #{post_sql} ON #{klass::DBTABLE} (#{idx})"
312
312
  end
313
+ end
314
+
315
+ # Create join tables if needed. Join tables are used in
316
+ # 'many_to_many' relations.
317
+
318
+ if klass.__meta and joins = klass.__meta[:sql_join]
319
+ for data in joins
320
+ # the class to join to and some options.
321
+ join_class, options = *data
322
+
323
+ # gmosx: dont use DBTABLE here, perhaps the join class
324
+ # is not managed yet.
325
+ join_table = "#{Og::Utils.join_table(klass, join_class)}"
326
+ join_src = "#{Og::Utils.encode(klass)}_oid"
327
+ join_dst = "#{Og::Utils.encode(join_class)}_oid"
328
+ begin
329
+ exec "CREATE TABLE #{join_table} ( key1 integer, key2 integer )"
330
+ exec "CREATE INDEX #{join_table}_key1_idx ON #{join_table} (key1)"
331
+ exec "CREATE INDEX #{join_table}_key2_idx ON #{join_table} (key2)"
332
+ rescue => ex
333
+ if ex.errno == 1050 # table already exists.
334
+ $log.debug "Join table already exists" if $DBG
335
+ else
336
+ raise
337
+ end
338
+ end
339
+ end
313
340
  end
314
341
  end
315
342
 
@@ -2,7 +2,7 @@
2
2
  # * George Moschovitis <gm@navel.gr>
3
3
  #
4
4
  # (c) 2004 Navel, all rights reserved.
5
- # $Id: psql.rb 159 2004-11-18 10:18:30Z gmosx $
5
+ # $Id: psql.rb 185 2004-12-10 13:29:09Z gmosx $
6
6
 
7
7
  require "postgres"
8
8
 
@@ -268,8 +268,8 @@ class PsqlBackend < Og::Backend
268
268
 
269
269
  # Create indices
270
270
 
271
- if klass.__meta
272
- for data in klass.__meta[:sql_index]
271
+ if klass.__meta and indices = klass.__meta[:sql_index]
272
+ for data in indices
273
273
  idx, options = *data
274
274
  idx = idx.to_s
275
275
  pre_sql, post_sql = options[:pre], options[:post]
@@ -304,6 +304,47 @@ class PsqlBackend < Og::Backend
304
304
  raise
305
305
  end
306
306
  end
307
+
308
+ # Create join tables if needed. Join tables are used in
309
+ # 'many_to_many' relations.
310
+
311
+ if klass.__meta and joins = klass.__meta[:sql_join]
312
+ for data in joins
313
+ # the class to join to and some options.
314
+ join_class, options = *data
315
+
316
+ # gmosx: dont use DBTABLE here, perhaps the join class
317
+ # is not managed yet.
318
+ join_table = "#{Og::Utils.join_table(klass, join_class)}"
319
+ join_src = "#{Og::Utils.encode(klass)}_oid"
320
+ join_dst = "#{Og::Utils.encode(join_class)}_oid"
321
+ begin
322
+ exec "CREATE TABLE #{join_table} ( key1 integer, key2 integer )"
323
+ exec "CREATE INDEX #{join_table}_key1_idx ON #{join_table} (key1)"
324
+ exec "CREATE INDEX #{join_table}_key2_idx ON #{join_table} (key2)"
325
+ rescue => ex
326
+ # gmosx: any idea how to better test this?
327
+ if ex.to_s =~ /relation .* already exists/i
328
+ $log.debug "Join table already exists" if $DBG
329
+ else
330
+ raise
331
+ end
332
+ end
333
+ end
334
+ end
335
+
336
+ begin
337
+ exec(sql)
338
+ $log.info "Created join table '#{join_table}'."
339
+ rescue => ex
340
+ # gmosx: any idea how to better test this?
341
+ if ex.to_s =~ /relation .* already exists/i
342
+ $log.debug "Join table already exists" if $DBG
343
+ else
344
+ raise
345
+ end
346
+ end
347
+
307
348
  end
308
349
 
309
350
  # Drop the managed object table
@@ -2,9 +2,10 @@
2
2
  # * George Moschovitis <gm@navel.gr>
3
3
  #
4
4
  # (c) 2004 Navel, all rights reserved.
5
- # $Id: meta.rb 159 2004-11-18 10:18:30Z gmosx $
5
+ # $Id: meta.rb 185 2004-12-10 13:29:09Z gmosx $
6
6
 
7
7
  require 'og/backend'
8
+ require 'glue/inflector'
8
9
 
9
10
  module Og
10
11
 
@@ -68,15 +69,13 @@ module MetaLanguage
68
69
  }
69
70
  end
70
71
 
71
- # Implements a 'has_many' relation.
72
+ # Implements a 'has_one' relation.
72
73
  # Automatically enchants the calling class with helper methods.
73
74
  #
74
- # NOT IMPLEMENTED.
75
- #
76
75
  # Example:
77
76
  #
78
77
  # class MyObject
79
- # has_many AnotherObject, :children
78
+ # has_one :children, AnotherObject
80
79
  # end
81
80
  #
82
81
  # creates the code:
@@ -84,10 +83,23 @@ module MetaLanguage
84
83
  # def children; ... end
85
84
  #
86
85
  def has_one(klass, name, options = {})
86
+ # linkback is the property of the child object that 'links back'
87
+ # to this object.
88
+ linkback = options[:linkback] || "#{MetaUtils.expand(self)}_oid"
89
+
87
90
  module_eval %{
91
+ @@og_descendants ||= {}
92
+ @@og_descendants[#{klass}] = :#{linkback}
93
+
94
+ unless defined?(og_descendants)
95
+ def self.og_descendants
96
+ @@og_descendants
97
+ end
98
+ end
99
+
88
100
  def #{name}(extrasql = nil)
89
- $og.select_one("article_oid=\#\@oid \#\{extrasql\}", #{klass})
90
- end
101
+ $og.select_one("SELECT * FROM #{Utils.table(klass)} WHERE #{linkback}=\#\@oid \#\{extrasql\}", #{klass})
102
+ end
91
103
  }
92
104
  end
93
105
 
@@ -97,7 +109,7 @@ module MetaLanguage
97
109
  # Example:
98
110
  #
99
111
  # class MyObject
100
- # has_many AnotherObject, :children
112
+ # has_many :children, AnotherObject
101
113
  # end
102
114
  #
103
115
  # creates the code:
@@ -129,11 +141,106 @@ module MetaLanguage
129
141
  }
130
142
  end
131
143
 
144
+ # Implements a 'many_to_many' relation.
145
+ # Two objects are associated using an intermediate join table.
146
+ # Automatically enchants the calling class with helper methods.
147
+ #
148
+ # Example:
149
+ #
150
+ # class Article
151
+ # many_to_many :children, Categories
152
+ # end
153
+ #
154
+ # article.categories
155
+ # article.del_category
156
+ # article.add_category
157
+ # article.clear_categories
158
+ #
159
+ # category.articles
160
+ # ...
161
+ #
162
+ #--
163
+ # FIXME: make more compatible with other enchant methods.
164
+ #++
165
+ def many_to_many(klass, options = {})
166
+ name1 = options[:name1] || G::Inflector.name(self)
167
+ pname1 = options[:list_name1] || G::Inflector.plural_name(self)
168
+ name2 = options[:name2] || G::Inflector.name(klass)
169
+ pname2 = options[:list_name2] || G::Inflector.plural_name(klass)
170
+
171
+ # exit if the class is allready indirectly 'enchanted' from the
172
+ # other class of the many_to_many relation.
173
+ return if self.respond_to?(name1)
174
+
175
+ # Add some metadata to the class to allow for automatic join table
176
+ # calculation.
177
+ meta :sql_join, [klass, options]
178
+
179
+ # gmosx, FIXME: should I update descendants here ?
180
+
181
+ # enchant this class
182
+
183
+ module_eval %{
184
+ def #{pname2}(extrasql = nil)
185
+ $og.select("SELECT d.* FROM #{Utils.table(klass)} AS d, #{Utils.join_table(self, klass)} AS j WHERE j.key1=\#\@oid AND j.key2=d.oid \#\{extrasql\}", #{klass})
186
+ end
187
+
188
+ def #{pname2}_count(extrasql = nil)
189
+ $og.select("SELECT COUNT(*) FROM #{Utils.table(klass)} AS d, #{Utils.join_table(self, klass)} AS j WHERE j.key1=\#\@oid AND j.key2=d.oid \#\{extrasql\}", #{klass})
190
+ end
191
+
192
+ def add_#{name2}(obj, extra = nil)
193
+ $og.exec("INSERT INTO #{Utils.join_table(self, klass)} (key1, key2) VALUES (\#\@oid, \#\{obj.oid\})")
194
+ end
195
+
196
+ def del_#{name2}(obj_or_oid, extra = nil)
197
+ $og.exec("DELETE FROM #{Utils.join_table(self, klass)} WHERE key2=\#\{obj_or_oid.to_i\}")
198
+ end
199
+ alias_method :delete_#{name2}, :del_#{name2}
200
+
201
+ def clear_#{pname2}
202
+ $og.exec("DELETE FROM #{Utils.join_table(self, klass)} WHERE key1=\#\@oid")
203
+ end
204
+ }
205
+
206
+ # indirectly enchant the other class of the relation.
207
+
208
+ klass.module_eval %{
209
+ def #{pname1}(extrasql = nil)
210
+ $og.select("SELECT s.* FROM #{Utils.table(self)} AS s, #{Utils.join_table(self, klass)} AS j WHERE j.key2=\#\@oid AND j.key1=s.oid \#\{extrasql\}", #{self})
211
+ end
212
+
213
+ def #{pname1}_count(extrasql = nil)
214
+ $og.select("SELECT COUNT(*) FROM #{Utils.table(self)} AS s, #{Utils.join_table(self, klass)} AS j WHERE j.key2=\#\@oid AND j.key1=s.oid \#\{extrasql\}", #{self})
215
+ end
216
+
217
+ def add_#{name1}(obj, extra = nil)
218
+ $og.exec("INSERT INTO #{Utils.join_table(self, klass)} (key1, key2) VALUES (\#\{obj.oid\}, \#\@oid)")
219
+ end
220
+
221
+ def del_#{name1}(obj_or_oid, extra = nil)
222
+ $og.exec("DELETE FROM #{Utils.join_table(self, klass)} WHERE key1=\#\{obj_or_oid.to_i\}")
223
+ end
224
+ alias_method :delete_#{name1}, :del_#{name1}
225
+
226
+ def clear_#{pname1}
227
+ $og.exec("DELETE FROM #{Utils.join_table(self, klass)} WHERE key2=\#\@oid")
228
+ end
229
+ }
230
+ end
231
+ alias :has_and_belongs_to_many :many_to_many
232
+
132
233
  end
133
234
 
134
235
  end # module
135
236
 
136
- class Module # :nodoc: all
137
- include Og::MetaLanguage
237
+ # Include the meta-language extensions into Module. If the flag is
238
+ # false the developer is responsible for including the MetaLanguage
239
+ # module where needed.
240
+ #
241
+ if $og_include_meta_language
242
+ class Module # :nodoc: all
243
+ include Og::MetaLanguage
244
+ end
138
245
  end
139
246
 
@@ -2,7 +2,7 @@
2
2
  # * George Moschovitis <gm@navel.gr>
3
3
  #
4
4
  # (c) 2004 Navel, all rights reserved.
5
- # $Id$
5
+ # $Id: version.rb 175 2004-11-26 16:11:27Z gmosx $
6
6
 
7
7
  # The version of Og.
8
- $og_version = '0.5.0'
8
+ $og_version = '0.6.0'
metadata CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.8.1
3
3
  specification_version: 1
4
4
  name: og
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.5.0
7
- date: 2004-11-23
6
+ version: 0.6.0
7
+ date: 2004-12-10
8
8
  summary: Og (ObjectGraph)
9
9
  require_paths:
10
10
  - lib