og 0.8.0 → 0.9.3
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/AUTHORS +3 -4
- data/ChangeLog +418 -0
- data/LICENSE +1 -1
- data/README.og +20 -16
- data/RELEASES.og +18 -0
- data/Rakefile +5 -7
- data/examples/og/run.rb +2 -2
- data/lib/glue.rb +13 -9
- data/lib/glue/array.rb +1 -1
- data/lib/glue/cache.rb +1 -1
- data/lib/glue/flexob.rb +12 -0
- data/lib/glue/hash.rb +1 -1
- data/lib/glue/inflector.rb +2 -2
- data/lib/glue/logger.rb +4 -8
- data/lib/glue/misc.rb +14 -0
- data/lib/glue/number.rb +1 -1
- data/lib/glue/object.rb +26 -0
- data/lib/glue/pool.rb +1 -1
- data/lib/glue/property.rb +84 -91
- data/lib/glue/string.rb +1 -1
- data/lib/glue/time.rb +1 -1
- data/lib/glue/validation.rb +1 -1
- data/lib/og.rb +66 -65
- data/lib/og/backend.rb +1 -1
- data/lib/og/backends/mysql.rb +48 -52
- data/lib/og/backends/psql.rb +34 -37
- data/lib/og/connection.rb +15 -15
- data/lib/og/enchant.rb +16 -9
- data/lib/og/meta.rb +127 -54
- data/lib/og/mock.rb +18 -18
- data/lib/og/version.rb +7 -7
- data/test/tc_og.rb +41 -4
- metadata +6 -3
data/lib/og/backends/psql.rb
CHANGED
@@ -1,8 +1,6 @@
|
|
1
|
-
# code:
|
2
1
|
# * George Moschovitis <gm@navel.gr>
|
3
|
-
#
|
4
|
-
#
|
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}
|
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
|
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
|
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
|
386
|
+
end
|
data/lib/og/connection.rb
CHANGED
@@ -1,17 +1,15 @@
|
|
1
|
-
|
2
|
-
#
|
3
|
-
#
|
4
|
-
#
|
5
|
-
|
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
|
10
|
-
require
|
11
|
-
require
|
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
|
-
|
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
|
data/lib/og/enchant.rb
CHANGED
@@ -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
|
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
|
87
|
+
end
|
data/lib/og/meta.rb
CHANGED
@@ -1,8 +1,6 @@
|
|
1
|
-
# code:
|
2
1
|
# * George Moschovitis <gm@navel.gr>
|
3
|
-
#
|
4
|
-
#
|
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
|
-
#
|
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
|
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 #{
|
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 #{
|
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 #{
|
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
|
-
|
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 #{
|
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 #{
|
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 #{
|
237
|
+
Og.db.exec("INSERT INTO #{Backend.join_table(self, klass)} (key1, key2) VALUES (\#\@oid, \#\{obj.oid\})")
|
209
238
|
end
|
210
239
|
|
211
|
-
def
|
212
|
-
Og.db.exec("DELETE FROM #{
|
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 #{
|
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 #{
|
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 #{
|
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 #{
|
261
|
+
Og.db.exec("INSERT INTO #{Backend.join_table(self, klass)} (key1, key2) VALUES (\#\{obj.oid\}, \#\@oid)")
|
234
262
|
end
|
235
263
|
|
236
|
-
def
|
237
|
-
Og.db.exec("DELETE FROM #{
|
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 #{
|
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
|
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
|
-
|