og 0.16.0 → 0.17.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/CHANGELOG +485 -0
- data/README +35 -12
- data/Rakefile +4 -7
- data/benchmark/bench.rb +1 -1
- data/doc/AUTHORS +3 -3
- data/doc/RELEASES +153 -2
- data/doc/config.txt +0 -7
- data/doc/tutorial.txt +7 -0
- data/examples/README +5 -0
- data/examples/mysql_to_psql.rb +25 -50
- data/examples/run.rb +62 -77
- data/install.rb +1 -1
- data/lib/og.rb +45 -106
- data/lib/og/collection.rb +156 -0
- data/lib/og/entity.rb +131 -0
- data/lib/og/errors.rb +10 -15
- data/lib/og/manager.rb +115 -0
- data/lib/og/{mixins → mixin}/hierarchical.rb +43 -37
- data/lib/og/{mixins → mixin}/orderable.rb +35 -35
- data/lib/og/{mixins → mixin}/timestamped.rb +0 -6
- data/lib/og/{mixins → mixin}/tree.rb +0 -4
- data/lib/og/relation.rb +178 -0
- data/lib/og/relation/belongs_to.rb +14 -0
- data/lib/og/relation/has_many.rb +62 -0
- data/lib/og/relation/has_one.rb +17 -0
- data/lib/og/relation/joins_many.rb +69 -0
- data/lib/og/relation/many_to_many.rb +17 -0
- data/lib/og/relation/refers_to.rb +31 -0
- data/lib/og/store.rb +223 -0
- data/lib/og/store/filesys.rb +113 -0
- data/lib/og/store/madeleine.rb +4 -0
- data/lib/og/store/memory.rb +291 -0
- data/lib/og/store/mysql.rb +283 -0
- data/lib/og/store/psql.rb +238 -0
- data/lib/og/store/sql.rb +599 -0
- data/lib/og/store/sqlite.rb +190 -0
- data/lib/og/store/sqlserver.rb +262 -0
- data/lib/og/types.rb +19 -0
- data/lib/og/validation.rb +0 -4
- data/test/og/{mixins → mixin}/tc_hierarchical.rb +21 -23
- data/test/og/{mixins → mixin}/tc_orderable.rb +15 -14
- data/test/og/mixin/tc_timestamped.rb +38 -0
- data/test/og/store/tc_filesys.rb +71 -0
- data/test/og/tc_relation.rb +36 -0
- data/test/og/tc_store.rb +290 -0
- data/test/og/tc_types.rb +21 -0
- metadata +54 -40
- data/examples/mock_example.rb +0 -50
- data/lib/og/adapters/base.rb +0 -706
- data/lib/og/adapters/filesys.rb +0 -117
- data/lib/og/adapters/mysql.rb +0 -350
- data/lib/og/adapters/oracle.rb +0 -368
- data/lib/og/adapters/psql.rb +0 -272
- data/lib/og/adapters/sqlite.rb +0 -265
- data/lib/og/adapters/sqlserver.rb +0 -356
- data/lib/og/database.rb +0 -290
- data/lib/og/enchant.rb +0 -149
- data/lib/og/meta.rb +0 -407
- data/lib/og/testing/mock.rb +0 -165
- data/lib/og/typemacros.rb +0 -24
- data/test/og/adapters/tc_filesys.rb +0 -83
- data/test/og/adapters/tc_sqlite.rb +0 -86
- data/test/og/adapters/tc_sqlserver.rb +0 -96
- data/test/og/tc_automanage.rb +0 -46
- data/test/og/tc_lifecycle.rb +0 -105
- data/test/og/tc_many_to_many.rb +0 -61
- data/test/og/tc_meta.rb +0 -55
- data/test/og/tc_validation.rb +0 -89
- data/test/tc_og.rb +0 -364
data/lib/og/database.rb
DELETED
@@ -1,290 +0,0 @@
|
|
1
|
-
# * George Moschovitis <gm@navel.gr>
|
2
|
-
# (c) 2004-2005 Navel, all rights reserved.
|
3
|
-
# $Id: database.rb 19 2005-04-15 09:24:19Z gmosx $
|
4
|
-
|
5
|
-
require 'glue/logger'
|
6
|
-
require 'glue/attribute'
|
7
|
-
require 'glue/property'
|
8
|
-
require 'glue/array'
|
9
|
-
require 'glue/hash'
|
10
|
-
require 'glue/time'
|
11
|
-
require 'glue/pool'
|
12
|
-
require 'glue/aspects'
|
13
|
-
|
14
|
-
require 'og/enchant'
|
15
|
-
require 'og/meta'
|
16
|
-
|
17
|
-
module Og
|
18
|
-
|
19
|
-
# Encapsulates an Og Database.
|
20
|
-
|
21
|
-
class Database
|
22
|
-
include Enchant
|
23
|
-
|
24
|
-
# Managed class metadata
|
25
|
-
|
26
|
-
class ManagedClassMeta
|
27
|
-
# The managed class.
|
28
|
-
attr_accessor :klass
|
29
|
-
|
30
|
-
# A mapping of the database fields to the object properties.
|
31
|
-
attr_accessor :field_index
|
32
|
-
|
33
|
-
def initialize(klass = nil)
|
34
|
-
@klass = klass
|
35
|
-
@field_index = {}
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
# hash of configuration options.
|
40
|
-
|
41
|
-
attr_accessor :config
|
42
|
-
|
43
|
-
# The adapter that comunicates with the backend
|
44
|
-
# datastore.
|
45
|
-
|
46
|
-
attr_accessor :adapter
|
47
|
-
|
48
|
-
# Pool of connections.
|
49
|
-
|
50
|
-
attr_accessor :connection_pool
|
51
|
-
|
52
|
-
# Managed classes.
|
53
|
-
|
54
|
-
attr_accessor :managed_classes
|
55
|
-
|
56
|
-
# Initialize the database interface.
|
57
|
-
|
58
|
-
def initialize(config)
|
59
|
-
@config = config
|
60
|
-
|
61
|
-
# populate with default options if needed.
|
62
|
-
@config[:connection_count] ||= 5
|
63
|
-
|
64
|
-
Logger.info "Connecting to database '#{@config[:database]}' using the '#{@config[:adapter]}' adapter."
|
65
|
-
|
66
|
-
@adapter = Adapter.for_name(@config[:adapter])
|
67
|
-
|
68
|
-
self.class.drop!(config) if config[:drop]
|
69
|
-
|
70
|
-
@connection_pool = Pool.new
|
71
|
-
@managed_classes = SafeHash.new
|
72
|
-
|
73
|
-
@config[:connection_count].times do
|
74
|
-
@connection_pool << @adapter.new_connection(self)
|
75
|
-
end
|
76
|
-
|
77
|
-
auto_manage_classes if Og.auto_manage_classes
|
78
|
-
|
79
|
-
# use the newly created database.
|
80
|
-
Og.use(self)
|
81
|
-
end
|
82
|
-
|
83
|
-
# Shutdown the database interface.
|
84
|
-
|
85
|
-
def shutdown
|
86
|
-
for con in @connection_pool
|
87
|
-
con.close()
|
88
|
-
end
|
89
|
-
end
|
90
|
-
alias_method :close, :shutdown
|
91
|
-
|
92
|
-
# Get a connection from the pool to access the database.
|
93
|
-
# Stores the connection in a thread-local variable.
|
94
|
-
|
95
|
-
def get_connection
|
96
|
-
thread = Thread.current
|
97
|
-
|
98
|
-
unless conn = thread[:og_conn]
|
99
|
-
conn = @connection_pool.pop()
|
100
|
-
thread[:og_conn] = conn
|
101
|
-
end
|
102
|
-
|
103
|
-
return conn
|
104
|
-
end
|
105
|
-
alias_method :connection, :get_connection
|
106
|
-
|
107
|
-
# Restore an unused connection to the pool.
|
108
|
-
|
109
|
-
def put_connection
|
110
|
-
thread = Thread.current
|
111
|
-
|
112
|
-
if conn = thread[:og_conn]
|
113
|
-
thread[:og_conn] = nil
|
114
|
-
return @connection_pool.push(conn)
|
115
|
-
end
|
116
|
-
end
|
117
|
-
|
118
|
-
# Utility method, automatically restores a connection to the pool.
|
119
|
-
|
120
|
-
def connect(&block)
|
121
|
-
result = nil
|
122
|
-
|
123
|
-
begin
|
124
|
-
conn = get_connection
|
125
|
-
result = yield(conn)
|
126
|
-
ensure
|
127
|
-
put_connection
|
128
|
-
end
|
129
|
-
|
130
|
-
return result
|
131
|
-
end
|
132
|
-
alias_method :open, :connect
|
133
|
-
|
134
|
-
# Register a standard Ruby class as managed.
|
135
|
-
|
136
|
-
def manage(klass)
|
137
|
-
return if managed?(klass) or klass.ancestors.include?(Unmanageable)
|
138
|
-
|
139
|
-
@managed_classes[klass] = ManagedClassMeta.new(klass)
|
140
|
-
|
141
|
-
# Add standard og methods to the class.
|
142
|
-
convert(klass)
|
143
|
-
|
144
|
-
# Add helper methods to the class.
|
145
|
-
enchant(klass) if Og.enchant_managed_classes
|
146
|
-
end
|
147
|
-
|
148
|
-
# Helper method to set multiple managed classes.
|
149
|
-
|
150
|
-
def manage_classes(*klasses)
|
151
|
-
for klass in klasses
|
152
|
-
manage(klass)
|
153
|
-
end
|
154
|
-
end
|
155
|
-
|
156
|
-
# Use Ruby's advanced reflection features to find
|
157
|
-
# and manage all manageable classes. Manageable are all
|
158
|
-
# classes that include Properties or Metadata.
|
159
|
-
#--
|
160
|
-
# gmosx, FIXME: this automanage code is not elegant and slow
|
161
|
-
# should probably recode this, along with glue/property.rb
|
162
|
-
# FIXME: can this be optimized?
|
163
|
-
# TODO: find a better name.
|
164
|
-
#++
|
165
|
-
|
166
|
-
def auto_manage_classes
|
167
|
-
classes_to_manage = []
|
168
|
-
|
169
|
-
ObjectSpace.each_object(Class) do |c|
|
170
|
-
if c.respond_to?(:__props) and c.__props
|
171
|
-
classes_to_manage << c
|
172
|
-
end
|
173
|
-
end
|
174
|
-
|
175
|
-
Logger.debug "Og auto manages the following classes:"
|
176
|
-
Logger.debug "#{classes_to_manage.inspect}"
|
177
|
-
|
178
|
-
manage_classes(*classes_to_manage)
|
179
|
-
end
|
180
|
-
alias_method :auto_manage, :auto_manage_classes
|
181
|
-
|
182
|
-
# Stop managing a Ruby class
|
183
|
-
|
184
|
-
def unmanage(klass)
|
185
|
-
@managed_classes.delete(klass)
|
186
|
-
end
|
187
|
-
|
188
|
-
# Is this class managed?
|
189
|
-
|
190
|
-
def managed?(klass)
|
191
|
-
return @managed_classes.include?(klass)
|
192
|
-
end
|
193
|
-
|
194
|
-
# Add standard og functionality to the class
|
195
|
-
|
196
|
-
def convert(klass)
|
197
|
-
# gmosx: this check is needed to allow the developer to customize
|
198
|
-
# the sql generated for oid
|
199
|
-
|
200
|
-
@adapter.eval_og_oid(klass) unless klass.instance_methods.include?(:oid)
|
201
|
-
|
202
|
-
klass.class_eval %{
|
203
|
-
DBTABLE = "#{Adapter.table(klass)}"
|
204
|
-
DBSEQ = "#{Adapter.table(klass)}_oid_seq"
|
205
|
-
|
206
|
-
cattr_accessor :og_db
|
207
|
-
|
208
|
-
def to_i
|
209
|
-
@oid
|
210
|
-
end
|
211
|
-
}
|
212
|
-
|
213
|
-
# Set the adapter.
|
214
|
-
|
215
|
-
klass.og_db = self
|
216
|
-
|
217
|
-
# Create the schema for this class if not available.
|
218
|
-
|
219
|
-
@adapter.create_table(klass, self) if Og.create_schema
|
220
|
-
@adapter.eval_lifecycle_methods(klass, self)
|
221
|
-
end
|
222
|
-
|
223
|
-
# Automatically wrap connection methods.
|
224
|
-
|
225
|
-
def self.wrap_method(method, args)
|
226
|
-
args = args.split(/,/)
|
227
|
-
class_eval %{
|
228
|
-
def #{method}(#{args.join(", ")})
|
229
|
-
thread = Thread.current
|
230
|
-
|
231
|
-
unless conn = thread[:og_conn]
|
232
|
-
conn = @connection_pool.pop()
|
233
|
-
thread[:og_conn] = conn
|
234
|
-
end
|
235
|
-
|
236
|
-
return conn.#{method}(#{args.collect {|a| a.split(/=/)[0]}.join(", ")})
|
237
|
-
end
|
238
|
-
}
|
239
|
-
end
|
240
|
-
|
241
|
-
wrap_method :db, ''
|
242
|
-
wrap_method :create_table, 'klass'
|
243
|
-
wrap_method :drop_table, 'klass'
|
244
|
-
wrap_method :save, 'obj'; alias_method :<<, :save; alias_method :put, :save
|
245
|
-
wrap_method :insert, 'obj'
|
246
|
-
wrap_method :update, 'obj'
|
247
|
-
wrap_method :update_properties, 'update_sql, obj_or_oid, klass = nil'
|
248
|
-
wrap_method :pupdate, 'update_sql, obj_or_oid, klass = nil'
|
249
|
-
wrap_method :load, 'oid, klass'; alias_method :get, :load
|
250
|
-
wrap_method :load_by_oid, 'oid, klass'
|
251
|
-
wrap_method :load_by_name, 'name, klass'
|
252
|
-
wrap_method :load_all, 'klass, extrasql = nil'
|
253
|
-
wrap_method :select, 'sql, klass'
|
254
|
-
wrap_method :select_one, 'sql, klass'
|
255
|
-
wrap_method :count, 'sql, klass = nil'
|
256
|
-
wrap_method :delete, 'obj_or_oid, klass = nil'
|
257
|
-
wrap_method :prepare, 'sql'
|
258
|
-
wrap_method :query, 'sql'
|
259
|
-
wrap_method :exec, 'sql'
|
260
|
-
wrap_method :get_row, 'res'
|
261
|
-
wrap_method :transaction, '&block'
|
262
|
-
|
263
|
-
class << self
|
264
|
-
|
265
|
-
# Create a new database.
|
266
|
-
|
267
|
-
def create_db!(config)
|
268
|
-
adapter = Adapter.for_name(config[:adapter])
|
269
|
-
adapter.create_db(config[:database], config[:user], config[:password])
|
270
|
-
end
|
271
|
-
alias_method :create!, :create_db!
|
272
|
-
|
273
|
-
# Drop an existing database.
|
274
|
-
|
275
|
-
def drop_db!(config)
|
276
|
-
adapter = Adapter.for_name(config[:adapter])
|
277
|
-
adapter.drop_db(config[:database], config[:user], config[:password])
|
278
|
-
end
|
279
|
-
alias_method :drop!, :drop_db!
|
280
|
-
|
281
|
-
end
|
282
|
-
end
|
283
|
-
|
284
|
-
# Helper method
|
285
|
-
|
286
|
-
def self.connect(*args)
|
287
|
-
db = Database.new(*args)
|
288
|
-
end
|
289
|
-
|
290
|
-
end
|
data/lib/og/enchant.rb
DELETED
@@ -1,149 +0,0 @@
|
|
1
|
-
# * George Moschovitis <gm@navel.gr>
|
2
|
-
# (c) 2004-2005 Navel, all rights reserved.
|
3
|
-
# $Id: enchant.rb 1 2005-04-11 11:04:30Z gmosx $
|
4
|
-
|
5
|
-
module Og
|
6
|
-
|
7
|
-
module Enchant
|
8
|
-
|
9
|
-
# Enchant a managed class. Add useful DB related methods
|
10
|
-
# to the class and its instances.
|
11
|
-
|
12
|
-
def enchant(klass)
|
13
|
-
|
14
|
-
# Generate standard methods.
|
15
|
-
|
16
|
-
klass.module_eval %{
|
17
|
-
def self.create(*params, &block)
|
18
|
-
obj = #{klass}.new(*params, &block)
|
19
|
-
obj.save!
|
20
|
-
end
|
21
|
-
|
22
|
-
def self.save(obj)
|
23
|
-
@@og_db << obj
|
24
|
-
end
|
25
|
-
|
26
|
-
def self.load(oid_or_name)
|
27
|
-
@@og_db.load(oid_or_name, #{klass})
|
28
|
-
end
|
29
|
-
|
30
|
-
def self.get(oid_or_name)
|
31
|
-
@@og_db.load(oid_or_name, #{klass})
|
32
|
-
end
|
33
|
-
|
34
|
-
def self.[](oid_or_name)
|
35
|
-
@@og_db.load(oid_or_name, #{klass})
|
36
|
-
end
|
37
|
-
|
38
|
-
def self.load_all(extra_sql = nil)
|
39
|
-
@@og_db.load_all(#{klass}, extra_sql)
|
40
|
-
end
|
41
|
-
|
42
|
-
def self.all(extra_sql = nil)
|
43
|
-
@@og_db.load_all(#{klass}, extra_sql)
|
44
|
-
end
|
45
|
-
|
46
|
-
def self.update_all(set_sql, extra_sql = nil)
|
47
|
-
@@og_db.exec("UPDATE #{klass::DBTABLE} SET \#\{set_sql\} \#\{extra_sql\}")
|
48
|
-
end
|
49
|
-
|
50
|
-
def self.update(set_sql, extra_sql = nil)
|
51
|
-
@@og_db.exec("UPDATE #{klass::DBTABLE} SET \#\{set_sql\} \#\{extra_sql\}")
|
52
|
-
end
|
53
|
-
|
54
|
-
def self.count(sql = "SELECT COUNT(*) FROM #{klass::DBTABLE}")
|
55
|
-
@@og_db.count(sql, #{klass})
|
56
|
-
end
|
57
|
-
|
58
|
-
def self.select(sql)
|
59
|
-
@@og_db.select(sql, #{klass})
|
60
|
-
end
|
61
|
-
|
62
|
-
def self.find(sql)
|
63
|
-
@@og_db.select(sql, #{klass})
|
64
|
-
end
|
65
|
-
|
66
|
-
def self.select_one(sql)
|
67
|
-
@@og_db.select_one(sql, #{klass})
|
68
|
-
end
|
69
|
-
|
70
|
-
def self.find_one(sql)
|
71
|
-
@@og_db.select_one(sql, #{klass})
|
72
|
-
end
|
73
|
-
|
74
|
-
def self.one(sql)
|
75
|
-
@@og_db.select_one(sql, #{klass})
|
76
|
-
end
|
77
|
-
|
78
|
-
def self.delete(obj_or_oid)
|
79
|
-
@@og_db.delete(obj_or_oid, #{klass})
|
80
|
-
end
|
81
|
-
|
82
|
-
def self.transaction(&block)
|
83
|
-
@@og_db.transaction(&block)
|
84
|
-
end
|
85
|
-
|
86
|
-
def self.properties_and_relations
|
87
|
-
@@__meta[:props_and_relations]
|
88
|
-
end
|
89
|
-
|
90
|
-
def self.each(&block)
|
91
|
-
all.each(&block)
|
92
|
-
end
|
93
|
-
include Enumerable
|
94
|
-
|
95
|
-
def save
|
96
|
-
@@og_db << self
|
97
|
-
return self
|
98
|
-
end
|
99
|
-
alias_method :save!, :save
|
100
|
-
|
101
|
-
def reload
|
102
|
-
raise 'Cannot reload unmanaged object' unless @oid
|
103
|
-
res = @@og_db.query "SELECT * FROM #{klass::DBTABLE} WHERE oid=\#\{@oid\}"
|
104
|
-
og_read(@@og_db.get_row(res))
|
105
|
-
end
|
106
|
-
alias_method :reload!, :reload
|
107
|
-
|
108
|
-
def update_properties(updatesql)
|
109
|
-
@@og_db.pupdate(updatesql, self.oid, #{klass})
|
110
|
-
end
|
111
|
-
alias_method :pupdate, :update_properties
|
112
|
-
alias_method :update_property, :update_properties
|
113
|
-
|
114
|
-
def set_property(prop, value)
|
115
|
-
@@og_db.pupdate("\#\{prop\}=\#\{value\}", self.oid, #{klass})
|
116
|
-
end
|
117
|
-
|
118
|
-
def delete!
|
119
|
-
@@og_db.delete(self, #{klass})
|
120
|
-
end
|
121
|
-
}
|
122
|
-
|
123
|
-
# Generate finder methods.
|
124
|
-
|
125
|
-
code = ''
|
126
|
-
|
127
|
-
for p in klass.__props
|
128
|
-
code << %{
|
129
|
-
def self.find_by_#{p.name}(val, operator = '=', extra_sql = nil)
|
130
|
-
}
|
131
|
-
|
132
|
-
val = klass.og_db.adapter.typecast[p.klass].gsub(/\:s\:/, 'val')
|
133
|
-
|
134
|
-
if p.meta[:unique]
|
135
|
-
code << %{@@og_db.select_one("#{p.name}\#\{operator\}#{val} \#\{extra_sql\}", #{klass})}
|
136
|
-
else
|
137
|
-
code << %{@@og_db.select("#{p.name}\#\{operator\}#{val} \#\{extra_sql\}", #{klass})}
|
138
|
-
end
|
139
|
-
code << %{
|
140
|
-
end;
|
141
|
-
}
|
142
|
-
end
|
143
|
-
|
144
|
-
klass.module_eval(code)
|
145
|
-
end
|
146
|
-
|
147
|
-
end
|
148
|
-
|
149
|
-
end
|
data/lib/og/meta.rb
DELETED
@@ -1,407 +0,0 @@
|
|
1
|
-
# * George Moschovitis <gm@navel.gr>
|
2
|
-
# (c) 2005 Navel, all rights reserved.
|
3
|
-
# $Id: meta.rb 1 2005-04-11 11:04:30Z gmosx $
|
4
|
-
#--
|
5
|
-
# TODO:
|
6
|
-
# - precreate the meta sql statements as much as possible to
|
7
|
-
# avoid string interpolations.
|
8
|
-
#++
|
9
|
-
|
10
|
-
require 'glue/inflector'
|
11
|
-
require 'og/adapters/base'
|
12
|
-
require 'og/typemacros'
|
13
|
-
|
14
|
-
module Og
|
15
|
-
|
16
|
-
class Relation < Property
|
17
|
-
alias foreign_class klass
|
18
|
-
end
|
19
|
-
|
20
|
-
class Has < Relation; end
|
21
|
-
class HasMany < Has; end
|
22
|
-
class HasOne < Has; end
|
23
|
-
class ManyToMany < Has; end
|
24
|
-
class BelongsTo < Relation; end
|
25
|
-
class RefersTo < Relation; end
|
26
|
-
|
27
|
-
# Some useful meta-language utilities.
|
28
|
-
|
29
|
-
module MetaUtils # :nodoc: all
|
30
|
-
|
31
|
-
# Convert the klass to a string representation
|
32
|
-
# The leading module if available is removed.
|
33
|
-
#--
|
34
|
-
# gmosx, FIXME: unify with the ogutils.encode method?
|
35
|
-
#++
|
36
|
-
|
37
|
-
def self.expand(klass)
|
38
|
-
return klass.name.gsub(/^.*::/, '').gsub(/::/, '_').downcase
|
39
|
-
end
|
40
|
-
|
41
|
-
# Infer the target klass for a relation. When defining
|
42
|
-
# relations, tha target class is typically given. Some times
|
43
|
-
# in order to avoid forward declarations a symbol is given instead
|
44
|
-
# of a class. Other times on class is given at all, and
|
45
|
-
# the inflection mechanism is used to infer the class name.
|
46
|
-
#--
|
47
|
-
# This is not used yet.
|
48
|
-
#++
|
49
|
-
|
50
|
-
def self.resolve_class(name, klass)
|
51
|
-
klass ||= Inflector.camelize(name)
|
52
|
-
|
53
|
-
return klass if klass.is_a?(Class)
|
54
|
-
|
55
|
-
unless klass.is_a?(String) or klass.is_a?(Symbol)
|
56
|
-
raise 'Invalid class definition'
|
57
|
-
end
|
58
|
-
|
59
|
-
unless Object.const_get(klass.intern)
|
60
|
-
# Forward declaration.
|
61
|
-
Object.class_eval("class #{klass}; end")
|
62
|
-
end
|
63
|
-
|
64
|
-
return Object.const_get(klass)
|
65
|
-
end
|
66
|
-
|
67
|
-
end
|
68
|
-
|
69
|
-
# Implements a meta-language for manipulating og-managed objects
|
70
|
-
# and defining their relationships. The original idea comes
|
71
|
-
# from the excellent ActiveRecord library.
|
72
|
-
#
|
73
|
-
# Many more useful relations will be available soon.
|
74
|
-
|
75
|
-
module MetaLanguage
|
76
|
-
|
77
|
-
# Defines an SQL index. Useful for defining indiced
|
78
|
-
# over multiple columns.
|
79
|
-
|
80
|
-
def sql_index(index, options = {})
|
81
|
-
meta :sql_index, [index, options]
|
82
|
-
end
|
83
|
-
|
84
|
-
# Implements a 'belongs_to' relation.
|
85
|
-
# Automatically enchants the calling class with helper methods.
|
86
|
-
#
|
87
|
-
# Example:
|
88
|
-
#
|
89
|
-
# class MyObject
|
90
|
-
# belongs_to AnotherObject, :prop => :parent
|
91
|
-
# end
|
92
|
-
#
|
93
|
-
# creates the code:
|
94
|
-
#
|
95
|
-
# prop_accessor Fixnum, :parent_oid
|
96
|
-
# def parent; ... end
|
97
|
-
# def parent=(obj_or_oid); ... end
|
98
|
-
|
99
|
-
def belongs_to(name, klass, options = {})
|
100
|
-
prop_eval = "prop_accessor Fixnum, :#{name}_oid"
|
101
|
-
prop_eval << ", :sql => '#{options[:sql]}'" if options[:sql]
|
102
|
-
prop_eval << ", :extra_sql => '#{options[:extra_sql]}'" if options[:extra_sql]
|
103
|
-
|
104
|
-
meta :props_and_relations, BelongsTo.new(name, klass, :property => "#{name}_oid".intern)
|
105
|
-
|
106
|
-
module_eval %{
|
107
|
-
#{prop_eval}
|
108
|
-
|
109
|
-
def #{name}
|
110
|
-
Og.db.load_by_oid(@#{name}_oid, #{klass})
|
111
|
-
end
|
112
|
-
|
113
|
-
def #{name}=(obj_or_oid)
|
114
|
-
@#{name}_oid = obj_or_oid.to_i
|
115
|
-
end
|
116
|
-
}
|
117
|
-
end
|
118
|
-
|
119
|
-
# Implements a 'has_one' relation.
|
120
|
-
# Automatically enchants the calling class with helper methods.
|
121
|
-
#
|
122
|
-
# Example:
|
123
|
-
#
|
124
|
-
# class MyObject
|
125
|
-
# has_one :child, TheClass
|
126
|
-
# has_one :article
|
127
|
-
# end
|
128
|
-
#
|
129
|
-
# creates the code:
|
130
|
-
#
|
131
|
-
# ...
|
132
|
-
|
133
|
-
def has_one(name, klass = nil, options = {})
|
134
|
-
|
135
|
-
# linkback is the property of the child object that 'links back'
|
136
|
-
# to this object.
|
137
|
-
|
138
|
-
linkback = (options[:linkback].to_s || "#{MetaUtils.expand(self)}_oid").to_s
|
139
|
-
|
140
|
-
meta :descendants, klass, linkback
|
141
|
-
meta :props_and_relations, HasOne.new(name, klass, options)
|
142
|
-
|
143
|
-
module_eval %{
|
144
|
-
def #{name}(extrasql = nil)
|
145
|
-
Og.db.select_one("SELECT * FROM #{Og::Adapter.table(klass)} WHERE #{linkback}=\#\@oid \#\{extrasql\}", #{klass})
|
146
|
-
end
|
147
|
-
|
148
|
-
def delete_#{name}(extrasql = nil)
|
149
|
-
Og.db.exec("DELETE FROM #{Og::Adapter.table(klass)} WHERE #{linkback}=\#\@oid \#\{extrasql\}")
|
150
|
-
end
|
151
|
-
}
|
152
|
-
end
|
153
|
-
|
154
|
-
# Implements a 'has_many' relation.
|
155
|
-
# Automatically enchants the calling class with helper methods.
|
156
|
-
#
|
157
|
-
# Options:
|
158
|
-
#
|
159
|
-
# [+linkback+]
|
160
|
-
#
|
161
|
-
# [+order+]
|
162
|
-
#
|
163
|
-
# [+extrasql+]
|
164
|
-
#
|
165
|
-
# Example:
|
166
|
-
#
|
167
|
-
# class MyObject
|
168
|
-
# has_many :articles, Article
|
169
|
-
# end
|
170
|
-
#
|
171
|
-
# creates the code:
|
172
|
-
#
|
173
|
-
# obj.articles
|
174
|
-
# obj.add_article(article)
|
175
|
-
# obj.add_article do |a|
|
176
|
-
# a.title = 'Title'
|
177
|
-
# a.body = 'Body'
|
178
|
-
# end
|
179
|
-
|
180
|
-
def has_many(name, klass, options = {})
|
181
|
-
name_s = Inflector.singularize(name.to_s)
|
182
|
-
|
183
|
-
# linkback is the property of the child object that 'links back'
|
184
|
-
# to this object.
|
185
|
-
|
186
|
-
linkback = (options[:linkback] || "#{MetaUtils.expand(self)}_oid").to_s
|
187
|
-
|
188
|
-
if order = options[:order]
|
189
|
-
order = "ORDER BY #{order}"
|
190
|
-
end
|
191
|
-
default_extrasql = "#{order}#{options[:sql]}"
|
192
|
-
|
193
|
-
# keep belongs to metadata, useful for
|
194
|
-
# reflection/scaffolding.
|
195
|
-
|
196
|
-
meta :descendants, klass, linkback
|
197
|
-
meta :props_and_relations, HasMany.new(name, klass, options)
|
198
|
-
|
199
|
-
code = %{
|
200
|
-
def #{name}(extrasql = nil)
|
201
|
-
Og.db.select("SELECT * FROM #{Og::Adapter.table(klass)} WHERE #{linkback}=\#\@oid #{default_extrasql} \#\{extrasql\}", #{klass})
|
202
|
-
end
|
203
|
-
|
204
|
-
def #{name}_count(extrasql = nil)
|
205
|
-
Og.db.count("SELECT COUNT(*) FROM #{Og::Adapter.table(klass)} WHERE #{linkback}=\#\@oid \#\{extrasql\}")
|
206
|
-
end
|
207
|
-
|
208
|
-
def add_#{name_s}(obj = nil)
|
209
|
-
yield(obj = #{klass}.new) unless obj
|
210
|
-
}
|
211
|
-
if Og.check_implicit_graph_changes
|
212
|
-
code << %{
|
213
|
-
if obj.#{linkback}
|
214
|
-
Logger.warn "Implicit graph change for object #{obj.inspect}"
|
215
|
-
end
|
216
|
-
}
|
217
|
-
end
|
218
|
-
code << %{
|
219
|
-
obj.#{linkback} = @oid
|
220
|
-
obj.save!
|
221
|
-
end
|
222
|
-
|
223
|
-
def delete_all_#{name}(extrasql = nil)
|
224
|
-
Og.db.exec("DELETE FROM #{Og::Adapter.table(klass)} WHERE #{linkback}=\#\@oid \#\{extrasql\}")
|
225
|
-
end
|
226
|
-
}
|
227
|
-
|
228
|
-
module_eval(code)
|
229
|
-
end
|
230
|
-
|
231
|
-
# Implements a 'many_to_many' relation.
|
232
|
-
# Two objects are associated using an intermediate join table.
|
233
|
-
# Automatically enchants the calling class with helper methods.
|
234
|
-
#
|
235
|
-
# Options:
|
236
|
-
#
|
237
|
-
# Example:
|
238
|
-
#
|
239
|
-
# class Article
|
240
|
-
# many_to_many :categories, Category, :linkback => articles
|
241
|
-
# end
|
242
|
-
#
|
243
|
-
# article.categories
|
244
|
-
# article.del_category
|
245
|
-
# article.add_category
|
246
|
-
# article.add_category { |c| ... }
|
247
|
-
# article.clear_categories
|
248
|
-
#
|
249
|
-
# category.articles
|
250
|
-
# ...
|
251
|
-
#--
|
252
|
-
# FIXME: make more compatible with other enchant methods.
|
253
|
-
#++
|
254
|
-
|
255
|
-
def many_to_many(name, klass, options = {})
|
256
|
-
list_o = name.to_s
|
257
|
-
prop_o = Inflector.singularize(list_o)
|
258
|
-
list_m = (options[:linkback] || Inflector.plural_name(self)).to_s
|
259
|
-
prop_m = Inflector.singularize(list_m)
|
260
|
-
|
261
|
-
# Exit if the class is allready indirectly 'enchanted' from the
|
262
|
-
# other class of the many_to_many relation.
|
263
|
-
|
264
|
-
return if self.respond_to?(prop_m)
|
265
|
-
|
266
|
-
# Add some metadata to the class to allow for automatic join table
|
267
|
-
# calculation.
|
268
|
-
|
269
|
-
meta :sql_join, name, klass, options
|
270
|
-
|
271
|
-
# FIXME: should add metadata for cascading delete.
|
272
|
-
|
273
|
-
meta :props_and_relations, ManyToMany.new(prop_o, klass)
|
274
|
-
klass.meta :props_and_relations, ManyToMany.new(prop_m, self)
|
275
|
-
|
276
|
-
# Enchant this class
|
277
|
-
|
278
|
-
module_eval %{
|
279
|
-
def #{list_o}(extrasql = nil)
|
280
|
-
Og.db.select("SELECT d.* FROM #{Og::Adapter.table(klass)} d, #{Og::Adapter.join_table(self, klass, name)} j WHERE j.key1=\#\@oid AND j.key2=d.oid \#\{extrasql\}", #{klass})
|
281
|
-
end
|
282
|
-
|
283
|
-
def #{list_o}_count(extrasql = nil)
|
284
|
-
Og.db.select("SELECT COUNT(*) FROM #{Og::Adapter.table(klass)} d, #{Og::Adapter.join_table(self, klass, name)} j WHERE j.key1=\#\@oid AND j.key2=d.oid \#\{extrasql\}", #{klass})
|
285
|
-
end
|
286
|
-
|
287
|
-
def add_#{prop_o}(obj = nil)
|
288
|
-
yield(obj = #{klass}.new) unless obj
|
289
|
-
obj.save! unless obj.oid
|
290
|
-
Og.db.exec("INSERT INTO #{Og::Adapter.join_table(self, klass, name)} (key1, key2) VALUES (\#\@oid, \#\{obj.oid\})")
|
291
|
-
end
|
292
|
-
|
293
|
-
def delete_#{prop_o}(obj_or_oid)
|
294
|
-
Og.db.exec("DELETE FROM #{Og::Adapter.join_table(self, klass, name)} WHERE key2=\#\{obj_or_oid.to_i\}")
|
295
|
-
end
|
296
|
-
|
297
|
-
def clear_#{list_o}
|
298
|
-
Og.db.exec("DELETE FROM #{Og::Adapter.join_table(self, klass, name)} WHERE key1=\#\@oid")
|
299
|
-
end
|
300
|
-
}
|
301
|
-
|
302
|
-
# indirectly enchant the other class of the relation.
|
303
|
-
|
304
|
-
klass.module_eval %{
|
305
|
-
def #{list_m}(extrasql = nil)
|
306
|
-
Og.db.select("SELECT s.* FROM #{Og::Adapter.table(self)} s, #{Og::Adapter.join_table(self, klass, name)} j WHERE j.key2=\#\@oid AND j.key1=s.oid \#\{extrasql\}", #{self})
|
307
|
-
end
|
308
|
-
|
309
|
-
def #{list_m}_count(extrasql = nil)
|
310
|
-
Og.db.select("SELECT COUNT(*) FROM #{Og::Adapter.table(self)} s, #{Og::Adapter.join_table(self, klass, name)} j WHERE j.key2=\#\@oid AND j.key1=s.oid \#\{extrasql\}", #{self})
|
311
|
-
end
|
312
|
-
|
313
|
-
def add_#{prop_m}(obj = nil)
|
314
|
-
yield(obj = #{self}.new) unless obj
|
315
|
-
obj.save! unless obj.oid
|
316
|
-
Og.db.exec("INSERT INTO #{Og::Adapter.join_table(self, klass, name)} (key1, key2) VALUES (\#\{obj.oid\}, \#\@oid)")
|
317
|
-
end
|
318
|
-
|
319
|
-
def delete_#{prop_m}(obj_or_oid)
|
320
|
-
Og.db.exec("DELETE FROM #{Og::Adapter.join_table(self, klass, name)} WHERE key1=\#\{obj_or_oid.to_i\}")
|
321
|
-
end
|
322
|
-
|
323
|
-
def clear_#{list_m}
|
324
|
-
Og.db.exec("DELETE FROM #{Og::Adapter.join_table(self, klass, name)} WHERE key2=\#\@oid")
|
325
|
-
end
|
326
|
-
}
|
327
|
-
end
|
328
|
-
alias :has_and_belongs_to_many :many_to_many
|
329
|
-
|
330
|
-
# Implements a 'refers_to' relation.
|
331
|
-
# This is a one-way version of the 'has_one'/'belongs_to'
|
332
|
-
# relations. The target object cannot link back to the source
|
333
|
-
# object.
|
334
|
-
# This is in fact EXACTLY the same as belongs_to with a
|
335
|
-
# different name (!!!!)
|
336
|
-
#
|
337
|
-
# Automatically enchants the calling class with helper methods.
|
338
|
-
#
|
339
|
-
#
|
340
|
-
# Example:
|
341
|
-
#
|
342
|
-
# class MyObject
|
343
|
-
# refers_to article, Article
|
344
|
-
# end
|
345
|
-
#
|
346
|
-
# creates the code:
|
347
|
-
#
|
348
|
-
# prop_accessor Fixnum, :article_oid
|
349
|
-
# def article; ... end
|
350
|
-
# def article=(obj_or_oid); ... end
|
351
|
-
|
352
|
-
def refers_to(name, klass, options = {})
|
353
|
-
prop_eval = "prop_accessor Fixnum, :#{name}_oid"
|
354
|
-
prop_eval << ", :sql => '#{options[:sql]}'" if options[:sql]
|
355
|
-
prop_eval << ", :extra_sql => '#{options[:extra_sql]}'" if options[:extra_sql]
|
356
|
-
|
357
|
-
meta :props_and_relations, RefersTo.new(name, klass, :property => "#{name}_oid".intern)
|
358
|
-
klass.meta :descendants, self, "#{name}_oid".intern
|
359
|
-
|
360
|
-
module_eval %{
|
361
|
-
#{prop_eval}
|
362
|
-
|
363
|
-
def #{name}
|
364
|
-
Og.db.load_by_oid(@#{name}_oid, #{klass})
|
365
|
-
end
|
366
|
-
|
367
|
-
def #{name}=(obj_or_oid)
|
368
|
-
@#{name}_oid = obj_or_oid.to_i
|
369
|
-
end
|
370
|
-
}
|
371
|
-
end
|
372
|
-
|
373
|
-
# Declares that this class can join with another class.
|
374
|
-
# The join parameters are given so the join-compatible
|
375
|
-
# methods are generated.
|
376
|
-
|
377
|
-
def joins(klass, options = {})
|
378
|
-
meta :joins, klass, options
|
379
|
-
end
|
380
|
-
|
381
|
-
end
|
382
|
-
|
383
|
-
end
|
384
|
-
|
385
|
-
# Include the meta-language extensions into Module. If the flag is
|
386
|
-
# false the developer is responsible for including the MetaLanguage
|
387
|
-
# module where needed.
|
388
|
-
#
|
389
|
-
# By default this is FALSE, to avoid polution of the Module object.
|
390
|
-
# However if you include a prop_accessor or a managed Mixin in your
|
391
|
-
# object MetaLanguage gets automatically extended in the class.
|
392
|
-
|
393
|
-
if Og.include_meta_language
|
394
|
-
class Module # :nodoc: all
|
395
|
-
include Og::MetaLanguage
|
396
|
-
|
397
|
-
=begin
|
398
|
-
A hack to avoid forward references. Does not work
|
399
|
-
with namespave modules though. Any idea?
|
400
|
-
-> try TOPLEVEL_BINDING
|
401
|
-
|
402
|
-
def const_missing(sym)
|
403
|
-
eval "class ::#{sym}; end; return #{sym}"
|
404
|
-
end
|
405
|
-
=end
|
406
|
-
end
|
407
|
-
end
|