og 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
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