og 0.8.0 → 0.9.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,8 +1,6 @@
1
- # code:
2
1
  # * George Moschovitis <gm@navel.gr>
3
- #
4
- # (c) 2004 Navel, all rights reserved.
5
- # $Id: psql.rb 194 2004-12-20 20:23:57Z gmosx $
2
+ # (c) 2004-2005 Navel, all rights reserved.
3
+ # $Id: psql.rb 248 2005-01-31 13:38:34Z gmosx $
6
4
 
7
5
  require 'postgres'
8
6
 
@@ -10,16 +8,14 @@ require 'og/backend'
10
8
 
11
9
  class Og
12
10
 
13
- # = PsqlBackend
14
- #
15
11
  # Implements a PostgreSQL powered backend.
16
12
  # This backend is compatible with Michael Neumann's postgres-pr
17
13
  # pure ruby driver.
18
- #
14
+
19
15
  class PsqlBackend < Og::Backend
20
16
 
21
17
  # A mapping between Ruby and SQL types.
22
- #
18
+
23
19
  TYPEMAP = {
24
20
  Integer => 'integer',
25
21
  Fixnum => 'integer',
@@ -34,7 +30,7 @@ class PsqlBackend < Og::Backend
34
30
  }
35
31
 
36
32
  # Intitialize the connection to the RDBMS.
37
- #
33
+
38
34
  def initialize(config)
39
35
  begin
40
36
  @conn = PGconn.connect(nil, nil, nil, nil, config[:database],
@@ -55,7 +51,7 @@ class PsqlBackend < Og::Backend
55
51
  # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
56
52
 
57
53
  # Escape an SQL string
58
- #
54
+
59
55
  def self.escape(str)
60
56
  return nil unless str
61
57
  return PGconn.escape(str)
@@ -63,7 +59,7 @@ class PsqlBackend < Og::Backend
63
59
 
64
60
  # Convert a ruby time to an sql timestamp.
65
61
  # TODO: Optimize this
66
- #
62
+
67
63
  def self.timestamp(time = Time.now)
68
64
  return nil unless time
69
65
  return time.strftime("%Y-%m-%d %H:%M:%S")
@@ -71,7 +67,7 @@ class PsqlBackend < Og::Backend
71
67
 
72
68
  # Output YYY-mm-dd
73
69
  # TODO: Optimize this
74
- #
70
+
75
71
  def self.date(date)
76
72
  return nil unless date
77
73
  return "#{date.year}-#{date.month}-#{date.mday}"
@@ -79,14 +75,14 @@ class PsqlBackend < Og::Backend
79
75
 
80
76
  # Parse sql datetime
81
77
  # TODO: Optimize this
82
- #
78
+
83
79
  def self.parse_timestamp(str)
84
80
  return Time.parse(str)
85
81
  end
86
82
 
87
83
  # Input YYYY-mm-dd
88
84
  # TODO: Optimize this
89
- #
85
+
90
86
  def self.parse_date(str)
91
87
  return nil unless str
92
88
  return Date.strptime(str)
@@ -98,7 +94,7 @@ class PsqlBackend < Og::Backend
98
94
  # portable.
99
95
  #
100
96
  # FIXME: add extra handling for float.
101
- #
97
+
102
98
  def self.write_prop(p)
103
99
  if p.klass.ancestors.include?(Integer)
104
100
  return "#\{@#{p.symbol} || 'NULL'\}"
@@ -111,7 +107,7 @@ class PsqlBackend < Og::Backend
111
107
  elsif p.klass.ancestors.include?(Date)
112
108
  return %|#\{@#{p.symbol} ? "'#\{Og::PsqlBackend.date(@#{p.symbol})\}'" : 'NULL'\}|
113
109
  elsif p.klass.ancestors.include?(TrueClass)
114
- return "#\{@#{p.symbol} || 'NULL'\}"
110
+ return "#\{@#{p.symbol} ? \"'t'\" : 'NULL' \}"
115
111
  else
116
112
  return %|#\{@#{p.symbol} ? "'#\{Og::PsqlBackend.escape(@#{p.symbol}.to_yaml)\}'" : "''"\}|
117
113
  end
@@ -119,7 +115,7 @@ class PsqlBackend < Og::Backend
119
115
 
120
116
  # Return an evaluator for reading the property.
121
117
  # No need to optimize this, used only to precalculate code.
122
- #
118
+
123
119
  def self.read_prop(p, idx)
124
120
  if p.klass.ancestors.include?(Integer)
125
121
  return "res.getvalue(tuple, #{idx}).to_i()"
@@ -132,7 +128,7 @@ class PsqlBackend < Og::Backend
132
128
  elsif p.klass.ancestors.include?(Date)
133
129
  return "Og::PsqlBackend.parse_date(res.getvalue(tuple, #{idx}))"
134
130
  elsif p.klass.ancestors.include?(TrueClass)
135
- return "('true' == res.getvalue(tuple, #{idx}))"
131
+ return %|('t' == res.getvalue(tuple, #{idx}))|
136
132
  else
137
133
  return "YAML::load(res.getvalue(tuple, #{idx}))"
138
134
  end
@@ -140,7 +136,7 @@ class PsqlBackend < Og::Backend
140
136
 
141
137
  # Returns the code that actually inserts the object into the
142
138
  # database. Returns the code as String.
143
- #
139
+
144
140
  def self.insert_code(klass, sql, pre_cb, post_cb)
145
141
  %{
146
142
  #{pre_cb}
@@ -153,7 +149,7 @@ class PsqlBackend < Og::Backend
153
149
 
154
150
  # generate the mapping of the database fields to the
155
151
  # object properties.
156
- #
152
+
157
153
  def self.calc_field_index(klass, og)
158
154
  res = og.query "SELECT * FROM #{klass::DBTABLE} LIMIT 1"
159
155
  meta = og.managed_classes[klass]
@@ -164,7 +160,7 @@ class PsqlBackend < Og::Backend
164
160
  end
165
161
 
166
162
  # Generate the property for oid
167
- #
163
+
168
164
  def self.eval_og_oid(klass)
169
165
  klass.class_eval %{
170
166
  prop_accessor :oid, Fixnum, :sql => "integer PRIMARY KEY"
@@ -176,28 +172,28 @@ class PsqlBackend < Og::Backend
176
172
  # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
177
173
 
178
174
  # Create the database.
179
- #
175
+
180
176
  def self.create_db(database, user = nil, password = nil)
181
177
  Logger.info "Creating database '#{database}'."
182
178
  `createdb #{database} -U #{user}`
183
179
  end
184
180
 
185
181
  # Drop the database.
186
- #
182
+
187
183
  def self.drop_db(database, user = nil, password = nil)
188
184
  Logger.info "Dropping database '#{database}'."
189
185
  `dropdb #{database} -U #{user}`
190
186
  end
191
187
 
192
- # Execute an SQL query and return the result
193
- #
188
+ # Execute an SQL query and return the result.
189
+
194
190
  def query(sql)
195
191
  Logger.debug sql if $DBG
196
192
  return @conn.exec(sql)
197
193
  end
198
194
 
199
195
  # Execute an SQL query, no result returned.
200
- #
196
+
201
197
  def exec(sql)
202
198
  Logger.debug sql if $DBG
203
199
  res = @conn.exec(sql)
@@ -206,7 +202,7 @@ class PsqlBackend < Og::Backend
206
202
 
207
203
  # Execute an SQL query and return the result. Wrapped in a rescue
208
204
  # block.
209
- #
205
+
210
206
  def safe_query(sql)
211
207
  Logger.debug sql if $DBG
212
208
  begin
@@ -220,7 +216,7 @@ class PsqlBackend < Og::Backend
220
216
 
221
217
  # Execute an SQL query, no result returned. Wrapped in a rescue
222
218
  # block.
223
- #
219
+
224
220
  def safe_exec(sql)
225
221
  Logger.debug sql if $DBG
226
222
  begin
@@ -233,7 +229,7 @@ class PsqlBackend < Og::Backend
233
229
  end
234
230
 
235
231
  # Check if it is a valid resultset.
236
- #
232
+
237
233
  def valid?(res)
238
234
  return !(res.nil? or 0 == res.num_tuples)
239
235
  end
@@ -241,7 +237,7 @@ class PsqlBackend < Og::Backend
241
237
  # Create the managed object table. The properties of the
242
238
  # object are mapped to the table columns. Additional sql relations
243
239
  # and constrains are created (indicices, sequences, etc).
244
- #
240
+
245
241
  def create_table(klass)
246
242
  fields = create_fields(klass, TYPEMAP)
247
243
 
@@ -282,6 +278,7 @@ class PsqlBackend < Og::Backend
282
278
  # create the sequence for this table. Even if the table
283
279
  # uses the oids_seq, attempt to create it. This makes
284
280
  # the system more fault tolerant.
281
+
285
282
  begin
286
283
  exec "CREATE SEQUENCE #{klass::DBSEQ}"
287
284
  Logger.info "Created sequence '#{klass::DBSEQ}'."
@@ -336,15 +333,15 @@ class PsqlBackend < Og::Backend
336
333
 
337
334
  end
338
335
 
339
- # Drop the managed object table
340
- #
336
+ # Drop the managed object table.
337
+
341
338
  def drop_table(klass)
342
339
  super
343
340
  exec "DROP SEQUENCE #{klass::DBSEQ}"
344
341
  end
345
342
 
346
343
  # Deserialize one row of the resultset.
347
- #
344
+
348
345
  def deserialize_one(res, klass)
349
346
  return nil unless valid?(res)
350
347
 
@@ -359,9 +356,9 @@ class PsqlBackend < Og::Backend
359
356
  end
360
357
 
361
358
  # Deserialize all rows of the resultset.
362
- #
359
+
363
360
  def deserialize_all(res, klass)
364
- return nil unless valid?(res)
361
+ return [] unless valid?(res)
365
362
 
366
363
  entities = []
367
364
 
@@ -379,11 +376,11 @@ class PsqlBackend < Og::Backend
379
376
  end
380
377
 
381
378
  # Return a single integer value from the resultset.
382
- #
379
+
383
380
  def get_int(res, idx = 0)
384
381
  return res.getvalue(0, idx).to_i
385
382
  end
386
383
 
387
384
  end
388
385
 
389
- end # module
386
+ end
@@ -1,17 +1,15 @@
1
- # code:
2
- # * George Moschovitis <gm@navel.gr>
3
- #
4
- # (c) 2004 Navel, all rights reserved.
5
- # $Id: connection.rb 167 2004-11-23 14:03:10Z gmosx $
1
+ #--
2
+ # George Moschovitis <gm@navel.gr>
3
+ # (c) 2004-2005 Navel, all rights reserved.
4
+ # $Id: connection.rb 248 2005-01-31 13:38:34Z gmosx $
5
+ #++
6
6
 
7
7
  class Og;
8
8
 
9
- require "glue/property"
10
- require "glue/array"
11
- require "glue/time"
9
+ require 'glue/property'
10
+ require 'glue/array'
11
+ require 'glue/time'
12
12
 
13
- # = Connection
14
- #
15
13
  # A Connection to the Database. This file defines the skeleton
16
14
  # functionality. A backend specific implementation file (driver)
17
15
  # implements all methods.
@@ -20,20 +18,23 @@ require "glue/time"
20
18
  #
21
19
  # - support caching.
22
20
  # - support prepared statements.
23
- #
21
+
24
22
  class Connection
25
23
  # The frontend (Og) contains useful strucutres.
24
+
26
25
  attr_reader :og
27
26
 
28
27
  # The backend
28
+
29
29
  attr_reader :db
30
30
 
31
31
  # If set to true, the select methods deserialize the
32
32
  # resultset to create entities.
33
+
33
34
  attr_accessor :deserialize
34
35
 
35
36
  # Initialize a connection to the database
36
- #
37
+
37
38
  def initialize(og)
38
39
  @og = og
39
40
  @db = @og.config[:backend].new(@og.config)
@@ -189,9 +190,8 @@ class Connection
189
190
  # TODO: implement this as stored procedure? naaah.
190
191
  transaction do |tx|
191
192
  tx.exec "DELETE FROM #{klass::DBTABLE} WHERE oid=#{oid}"
192
-
193
- if cascade and klass.respond_to?(:og_descendants)
194
- klass.og_descendants.each do |dclass, linkback|
193
+ if cascade and klass.__meta.include?(:has)
194
+ klass.__meta[:has].each do |dclass, linkback|
195
195
  tx.exec "DELETE FROM #{dclass::DBTABLE} WHERE #{linkback}=#{oid}"
196
196
  end
197
197
  end
@@ -1,20 +1,18 @@
1
- # code:
2
1
  # * George Moschovitis <gm@navel.gr>
3
- #
4
- # (c) 2004 Navel, all rights reserved.
2
+ # (c) 2004-2005 Navel, all rights reserved.
5
3
  # $Id: meta.rb 198 2004-12-22 11:26:59Z gmosx $
6
4
 
7
5
  class Og
8
6
 
9
7
  module Enchant
10
8
 
11
- # Enchant a managed class. Add useful DB related methods to the
12
- # class and its instances.
13
- #
9
+ # Enchant a managed class. Add useful DB related methods
10
+ # to the class and its instances.
11
+
14
12
  def enchant(klass)
15
13
  klass.module_eval <<-"end_eval", __FILE__, __LINE__
16
- def self.create(*params)
17
- obj = #{klass}.new(*params)
14
+ def self.create(*params, &block)
15
+ obj = #{klass}.new(*params, &block)
18
16
  obj.save!
19
17
  end
20
18
 
@@ -26,6 +24,10 @@ module Enchant
26
24
  Og.db.load(oid_or_name, #{klass})
27
25
  end
28
26
 
27
+ def self.get(oid_or_name)
28
+ Og.db.load(oid_or_name, #{klass})
29
+ end
30
+
29
31
  def self.[](oid_or_name)
30
32
  Og.db.load(oid_or_name, #{klass})
31
33
  end
@@ -57,6 +59,11 @@ module Enchant
57
59
  def self.delete(obj_or_oid)
58
60
  Og.db.delete(obj_or_oid, #{klass})
59
61
  end
62
+
63
+ def each(&block)
64
+ all.each(&block)
65
+ end
66
+ include Enumerable
60
67
 
61
68
  def save
62
69
  Og.db << self
@@ -77,4 +84,4 @@ module Enchant
77
84
 
78
85
  end
79
86
 
80
- end # namespace
87
+ end
@@ -1,8 +1,6 @@
1
- # code:
2
1
  # * George Moschovitis <gm@navel.gr>
3
- #
4
- # (c) 2004 Navel, all rights reserved.
5
- # $Id: meta.rb 198 2004-12-22 11:26:59Z gmosx $
2
+ # (c) 2004-2005 Navel, all rights reserved.
3
+ # $Id: meta.rb 248 2005-01-31 13:38:34Z gmosx $
6
4
 
7
5
  require 'og/backend'
8
6
  require 'glue/inflector'
@@ -12,18 +10,42 @@ class Og
12
10
  # = MetaUtils
13
11
  #
14
12
  # Some useful meta-language utilities.
15
- #
13
+
16
14
  module MetaUtils # :nodoc: all
17
15
 
18
- # Conver the klass to a string representation
16
+ # Convert the klass to a string representation
19
17
  # The leading module if available is removed.
20
18
  #--
21
19
  # gmosx, FIXME: unify with the ogutils.encode method?
22
20
  #++
21
+
23
22
  def self.expand(klass)
24
23
  return klass.name.gsub(/^.*::/, '').gsub(/::/, '_').downcase
25
24
  end
26
25
 
26
+ # Infer the target klass for a relation. When defining
27
+ # relations, tha target class is typically given. Some times
28
+ # in order to avoid forward declarations a symbol is given instead
29
+ # of a class. Other times on class is given at all, and
30
+ # the inflection mechanism is used to infer the class name.
31
+
32
+ def self.resolve_class(name, klass)
33
+ klass ||= N::Inflector.camelize(name)
34
+
35
+ return klass if klass.is_a?(Class)
36
+
37
+ unless klass.is_a?(String) or klass.is_a?(Symbol)
38
+ raise 'Invalid class definition'
39
+ end
40
+
41
+ unless Object.const_get(klass.intern)
42
+ # Forward declaration.
43
+ Object.class_eval("class #{klass}; end")
44
+ end
45
+
46
+ return Object.const_get(klass)
47
+ end
48
+
27
49
  end
28
50
 
29
51
  # = MetaLanguage
@@ -33,11 +55,12 @@ end
33
55
  # from the excellent ActiveRecord library.
34
56
  #
35
57
  # Many more useful relations will be available soon.
36
- #
58
+
37
59
  module MetaLanguage
38
60
 
39
- # Defines an SQL index.
40
- #
61
+ # Defines an SQL index. Useful for defining indiced
62
+ # over multiple columns.
63
+
41
64
  def sql_index(index, options = {})
42
65
  meta :sql_index, [index, options]
43
66
  end
@@ -56,12 +79,14 @@ module MetaLanguage
56
79
  # prop_accessor Fixnum, :parent_oid
57
80
  # def parent; ... end
58
81
  # def parent=(obj_or_oid); ... end
59
- #
82
+
60
83
  def belongs_to(name, klass, options = {})
61
84
  prop_eval = "prop_accessor Fixnum, :#{name}_oid"
62
85
  prop_eval << ", :sql => '#{options[:sql]}'" if options[:sql]
63
86
  prop_eval << ", :extra_sql => '#{options[:extra_sql]}'" if options[:extra_sql]
64
87
 
88
+ meta :belongs_to, klass
89
+
65
90
  module_eval %{
66
91
  #{prop_eval}
67
92
 
@@ -81,31 +106,31 @@ module MetaLanguage
81
106
  # Example:
82
107
  #
83
108
  # class MyObject
84
- # has_one AnotherObject
109
+ # has_one :child, TheClass
110
+ # has_one :article
85
111
  # end
86
112
  #
87
113
  # creates the code:
88
114
  #
89
115
  # ...
90
- #
91
- def has_one(name, klass, options = {})
116
+
117
+ def has_one(name, klass = nil, options = {})
118
+
92
119
  # linkback is the property of the child object that 'links back'
93
120
  # to this object.
121
+
94
122
  linkback = options[:linkback] || "#{MetaUtils.expand(self)}_oid"
95
123
 
124
+ meta :has, [klass, linkback]
125
+
96
126
  module_eval %{
97
- @@og_descendants ||= {}
98
- @@og_descendants[#{klass}] = :#{linkback}
99
-
100
- unless defined?(og_descendants)
101
- def self.og_descendants
102
- @@og_descendants
103
- end
104
- end
105
-
106
127
  def #{name}(extrasql = nil)
107
- Og.db.select_one("SELECT * FROM #{Og::Backend.table(klass)} WHERE #{linkback}=\#\@oid \#\{extrasql\}", #{klass})
128
+ Og.db.select_one("SELECT * FROM #{Backend.table(klass)} WHERE #{linkback}=\#\@oid \#\{extrasql\}", #{klass})
108
129
  end
130
+
131
+ def delete_#{name}(extrasql = nil)
132
+ Og.db.exec("DELETE FROM #{Backend.table(klass)} WHERE #{linkback}=\#\@oid \#\{extrasql\}")
133
+ end
109
134
  }
110
135
  end
111
136
 
@@ -121,36 +146,37 @@ module MetaLanguage
121
146
  # creates the code:
122
147
  #
123
148
  # def children; ... end
124
- #
149
+
125
150
  def has_many(name, klass, options = {})
126
151
  name_s = N::Inflector.singularize(name.to_s)
127
152
 
128
153
  # linkback is the property of the child object that 'links back'
129
154
  # to this object.
155
+
130
156
  linkback = options[:linkback] || "#{MetaUtils.expand(self)}_oid"
131
157
 
158
+ # keep belongs to metadata, useful for
159
+ # reflection/scaffolding.
160
+
161
+ meta :has, [klass, linkback]
162
+
132
163
  module_eval %{
133
- @@og_descendants ||= {}
134
- @@og_descendants[#{klass}] = :#{linkback}
135
-
136
- unless defined?(og_descendants)
137
- def self.og_descendants
138
- @@og_descendants
139
- end
140
- end
141
-
142
164
  def #{name}(extrasql = nil)
143
- Og.db.select("SELECT * FROM #{Og::Backend.table(klass)} WHERE #{linkback}=\#\@oid \#\{extrasql\}", #{klass})
165
+ Og.db.select("SELECT * FROM #{Backend.table(klass)} WHERE #{linkback}=\#\@oid \#\{extrasql\}", #{klass})
144
166
  end
145
167
 
146
168
  def #{name}_count(extrasql = nil)
147
- Og.db.count("SELECT COUNT(*) FROM #{Og::Backend.table(klass)} WHERE #{linkback}=\#\@oid \#\{extrasql\}")
169
+ Og.db.count("SELECT COUNT(*) FROM #{Backend.table(klass)} WHERE #{linkback}=\#\@oid \#\{extrasql\}")
148
170
  end
149
171
 
150
172
  def add_#{name_s}(obj, extra = nil)
151
173
  obj.#{linkback} = @oid
152
174
  obj.save!
153
175
  end
176
+
177
+ def delete_all_#{name}(extrasql = nil)
178
+ Og.db.exec("DELETE FROM #{Backend.table(klass)} WHERE #{linkback}=\#\@oid \#\{extrasql\}")
179
+ end
154
180
  }
155
181
  end
156
182
 
@@ -173,10 +199,10 @@ module MetaLanguage
173
199
  #
174
200
  # category.articles
175
201
  # ...
176
- #
177
202
  #--
178
203
  # FIXME: make more compatible with other enchant methods.
179
204
  #++
205
+
180
206
  def many_to_many(name, klass, options = {})
181
207
  list_o = name.to_s
182
208
  prop_o = N::Inflector.singularize(list_o)
@@ -185,36 +211,38 @@ module MetaLanguage
185
211
 
186
212
  # exit if the class is allready indirectly 'enchanted' from the
187
213
  # other class of the many_to_many relation.
214
+
188
215
  return if self.respond_to?(prop_m)
189
216
 
190
217
  # Add some metadata to the class to allow for automatic join table
191
218
  # calculation.
219
+
192
220
  meta :sql_join, [klass, options]
193
221
 
194
- # gmosx, FIXME: should I update descendants here ?
222
+ meta :many_to_many, klass
223
+ klass.meta :many_to_many, self
195
224
 
196
225
  # enchant this class
197
226
 
198
227
  module_eval %{
199
228
  def #{list_o}(extrasql = nil)
200
- Og.db.select("SELECT d.* FROM #{Og::Backend.table(klass)} AS d, #{Og::Backend.join_table(self, klass)} AS j WHERE j.key1=\#\@oid AND j.key2=d.oid \#\{extrasql\}", #{klass})
229
+ Og.db.select("SELECT d.* FROM #{Backend.table(klass)} AS d, #{Backend.join_table(self, klass)} AS j WHERE j.key1=\#\@oid AND j.key2=d.oid \#\{extrasql\}", #{klass})
201
230
  end
202
231
 
203
232
  def #{list_o}_count(extrasql = nil)
204
- Og.db.select("SELECT COUNT(*) FROM #{Og::Backend.table(klass)} AS d, #{Og::Backend.join_table(self, klass)} AS j WHERE j.key1=\#\@oid AND j.key2=d.oid \#\{extrasql\}", #{klass})
233
+ Og.db.select("SELECT COUNT(*) FROM #{Backend.table(klass)} AS d, #{Backend.join_table(self, klass)} AS j WHERE j.key1=\#\@oid AND j.key2=d.oid \#\{extrasql\}", #{klass})
205
234
  end
206
235
 
207
236
  def add_#{prop_o}(obj, extra = nil)
208
- Og.db.exec("INSERT INTO #{Og::Backend.join_table(self, klass)} (key1, key2) VALUES (\#\@oid, \#\{obj.oid\})")
237
+ Og.db.exec("INSERT INTO #{Backend.join_table(self, klass)} (key1, key2) VALUES (\#\@oid, \#\{obj.oid\})")
209
238
  end
210
239
 
211
- def del_#{prop_o}(obj_or_oid, extra = nil)
212
- Og.db.exec("DELETE FROM #{Og::Backend.join_table(self, klass)} WHERE key2=\#\{obj_or_oid.to_i\}")
240
+ def delete_#{prop_o}(obj_or_oid, extra = nil)
241
+ Og.db.exec("DELETE FROM #{Backend.join_table(self, klass)} WHERE key2=\#\{obj_or_oid.to_i\}")
213
242
  end
214
- alias_method :delete_#{prop_o}, :del_#{prop_o}
215
243
 
216
244
  def clear_#{list_o}
217
- Og.db.exec("DELETE FROM #{Og::Backend.join_table(self, klass)} WHERE key1=\#\@oid")
245
+ Og.db.exec("DELETE FROM #{Backend.join_table(self, klass)} WHERE key1=\#\@oid")
218
246
  end
219
247
  }
220
248
 
@@ -222,40 +250,85 @@ module MetaLanguage
222
250
 
223
251
  klass.module_eval %{
224
252
  def #{list_m}(extrasql = nil)
225
- Og.db.select("SELECT s.* FROM #{Og::Backend.table(self)} AS s, #{Og::Backend.join_table(self, klass)} AS j WHERE j.key2=\#\@oid AND j.key1=s.oid \#\{extrasql\}", #{self})
253
+ Og.db.select("SELECT s.* FROM #{Backend.table(self)} AS s, #{Backend.join_table(self, klass)} AS j WHERE j.key2=\#\@oid AND j.key1=s.oid \#\{extrasql\}", #{self})
226
254
  end
227
255
 
228
256
  def #{list_m}_count(extrasql = nil)
229
- Og.db.select("SELECT COUNT(*) FROM #{Og::Backend.table(self)} AS s, #{Og::Backend.join_table(self, klass)} AS j WHERE j.key2=\#\@oid AND j.key1=s.oid \#\{extrasql\}", #{self})
257
+ Og.db.select("SELECT COUNT(*) FROM #{Backend.table(self)} AS s, #{Backend.join_table(self, klass)} AS j WHERE j.key2=\#\@oid AND j.key1=s.oid \#\{extrasql\}", #{self})
230
258
  end
231
259
 
232
260
  def add_#{prop_m}(obj, extra = nil)
233
- Og.db.exec("INSERT INTO #{Og::Backend.join_table(self, klass)} (key1, key2) VALUES (\#\{obj.oid\}, \#\@oid)")
261
+ Og.db.exec("INSERT INTO #{Backend.join_table(self, klass)} (key1, key2) VALUES (\#\{obj.oid\}, \#\@oid)")
234
262
  end
235
263
 
236
- def del_#{prop_m}(obj_or_oid, extra = nil)
237
- Og.db.exec("DELETE FROM #{Og::Backend.join_table(self, klass)} WHERE key1=\#\{obj_or_oid.to_i\}")
264
+ def delete_#{prop_m}(obj_or_oid, extra = nil)
265
+ Og.db.exec("DELETE FROM #{Backend.join_table(self, klass)} WHERE key1=\#\{obj_or_oid.to_i\}")
238
266
  end
239
- alias_method :delete_#{prop_m}, :del_#{prop_m}
240
267
 
241
268
  def clear_#{list_m}
242
- Og.db.exec("DELETE FROM #{Og::Backend.join_table(self, klass)} WHERE key2=\#\@oid")
269
+ Og.db.exec("DELETE FROM #{Backend.join_table(self, klass)} WHERE key2=\#\@oid")
243
270
  end
244
271
  }
245
272
  end
246
273
  alias :has_and_belongs_to_many :many_to_many
247
274
 
275
+ # Implements a 'refers_to' relation.
276
+ # This is a one-way version of the 'has_one'/'belongs_to'
277
+ # relations. The target object cannot link back to the source
278
+ # object.
279
+ # This is in fact EXACTLY the same as belongs_to with a
280
+ # different name (!!!!)
281
+ #
282
+ # Automatically enchants the calling class with helper methods.
283
+ #
284
+ #
285
+ # Example:
286
+ #
287
+ # class MyObject
288
+ # refers_to article, Article
289
+ # end
290
+ #
291
+ # creates the code:
292
+ #
293
+ # prop_accessor Fixnum, :article_oid
294
+ # def article; ... end
295
+ # def article=(obj_or_oid); ... end
296
+
297
+ def refers_to(name, klass, options = {})
298
+ prop_eval = "prop_accessor Fixnum, :#{name}_oid"
299
+ prop_eval << ", :sql => '#{options[:sql]}'" if options[:sql]
300
+ prop_eval << ", :extra_sql => '#{options[:extra_sql]}'" if options[:extra_sql]
301
+
302
+ meta :refers_to, klass
303
+ klass.meta :has, [self, "#{name}_oid"]
304
+
305
+ module_eval %{
306
+ #{prop_eval}
307
+
308
+ def #{name}
309
+ Og.db.load_by_oid(@#{name}_oid, #{klass})
310
+ end
311
+
312
+ def #{name}=(obj_or_oid)
313
+ @#{name}_oid = obj_or_oid.to_i
314
+ end
315
+ }
316
+ end
317
+
248
318
  end
249
319
 
250
- end # namespace
320
+ end
251
321
 
252
322
  # Include the meta-language extensions into Module. If the flag is
253
323
  # false the developer is responsible for including the MetaLanguage
254
324
  # module where needed.
255
- #
325
+ #
326
+ # By default this is FALSE, to avoid polution of the Module object.
327
+ # However if you include a prop_accessor or a managed Mixin in your
328
+ # object MetaLanguage gets automatically extended in the class.
329
+
256
330
  if Og.include_meta_language
257
331
  class Module # :nodoc: all
258
332
  include Og::MetaLanguage
259
333
  end
260
334
  end
261
-