og 0.9.5 → 0.10.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 +260 -0
- data/LICENSE +1 -0
- data/README.og +6 -5
- data/RELEASES.og +23 -0
- data/Rakefile +102 -92
- data/examples/og/mock_example.rb +0 -2
- data/examples/og/mysql_to_psql.rb +0 -2
- data/examples/og/run.rb +23 -22
- data/install.rb +44 -0
- data/lib/glue/array.rb +6 -10
- data/lib/glue/attribute.rb +0 -3
- data/lib/glue/cache.rb +1 -1
- data/lib/glue/inflector.rb +5 -5
- data/lib/glue/mixins.rb +3 -12
- data/lib/glue/number.rb +1 -1
- data/lib/glue/object.rb +7 -1
- data/lib/glue/property.rb +32 -22
- data/lib/glue/string.rb +13 -75
- data/lib/glue/time.rb +2 -2
- data/lib/glue/validation.rb +7 -11
- data/lib/og.rb +27 -261
- data/lib/og/adapter.rb +352 -0
- data/lib/og/adapters/mysql.rb +304 -0
- data/lib/og/adapters/psql.rb +286 -0
- data/lib/og/adapters/sqlite.rb +262 -0
- data/lib/og/backend.rb +1 -1
- data/lib/og/connection.rb +123 -87
- data/lib/og/database.rb +268 -0
- data/lib/og/meta.rb +23 -22
- data/lib/og/mock.rb +2 -3
- data/test/og/tc_lifecycle.rb +22 -25
- data/test/og/tc_sqlite.rb +87 -0
- data/test/tc_og.rb +61 -42
- metadata +35 -11
- data/lib/glue/macro.rb +0 -56
- data/lib/og/backends/mysql.rb +0 -370
- data/lib/og/backends/psql.rb +0 -386
- data/lib/og/backends/sqlite.rb +0 -383
- data/lib/og/version.rb +0 -9
data/lib/glue/time.rb
CHANGED
data/lib/glue/validation.rb
CHANGED
@@ -8,7 +8,7 @@ module N
|
|
8
8
|
# objects. Typically used in Validator objects but can be
|
9
9
|
# included in managed objects too.
|
10
10
|
#
|
11
|
-
#
|
11
|
+
# === Example
|
12
12
|
#
|
13
13
|
# class User
|
14
14
|
# prop_accessor :name, String
|
@@ -50,8 +50,6 @@ module N
|
|
50
50
|
|
51
51
|
module Validation
|
52
52
|
|
53
|
-
# = Errors
|
54
|
-
#
|
55
53
|
# Encapsulates a list of validation errors.
|
56
54
|
|
57
55
|
class Errors
|
@@ -159,8 +157,6 @@ module Validation
|
|
159
157
|
base.extend(MetaLanguage)
|
160
158
|
end
|
161
159
|
|
162
|
-
# = MetaLanguage
|
163
|
-
#
|
164
160
|
# Implements the Validation meta-language.
|
165
161
|
|
166
162
|
module MetaLanguage
|
@@ -172,9 +168,9 @@ module Validation
|
|
172
168
|
# Validates that the attributes have a values, ie they are
|
173
169
|
# neither nil or empty.
|
174
170
|
#
|
175
|
-
#
|
171
|
+
# === Example
|
176
172
|
#
|
177
|
-
# validate_value
|
173
|
+
# validate_value :param, :msg => 'No confirmation'
|
178
174
|
|
179
175
|
def validate_value(*params)
|
180
176
|
c = {
|
@@ -199,7 +195,7 @@ module Validation
|
|
199
195
|
|
200
196
|
# Validates the confirmation of +String+ attributes.
|
201
197
|
#
|
202
|
-
#
|
198
|
+
# === Example
|
203
199
|
#
|
204
200
|
# validate_confirmation :password, :msg => 'No confirmation'
|
205
201
|
|
@@ -228,7 +224,7 @@ module Validation
|
|
228
224
|
|
229
225
|
# Validates the format of +String+ attributes.
|
230
226
|
#
|
231
|
-
#
|
227
|
+
# === Example
|
232
228
|
#
|
233
229
|
# validate_format :name, :format => /$A*/, :msg => 'My error', :on => :create
|
234
230
|
|
@@ -263,7 +259,7 @@ module Validation
|
|
263
259
|
|
264
260
|
# Validates the length of +String+ attributes.
|
265
261
|
#
|
266
|
-
#
|
262
|
+
# === Example
|
267
263
|
#
|
268
264
|
# validate_length :name, :max => 30, :msg => 'Too long'
|
269
265
|
# validate_length :name, :min => 2, :msg => 'Too sort'
|
@@ -354,7 +350,7 @@ module Validation
|
|
354
350
|
# Validates that the attributes are included in
|
355
351
|
# an enumeration.
|
356
352
|
#
|
357
|
-
#
|
353
|
+
# === Example
|
358
354
|
#
|
359
355
|
# validate_inclusion :sex, :in => %w{ Male Female }, :msg => 'huh??'
|
360
356
|
# validate_inclusion :age, :in => 5..99
|
data/lib/og.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
# * George Moschovitis <gm@navel.gr>
|
2
2
|
# (c) 2004-2005 Navel, all rights reserved.
|
3
|
-
# $Id: og.rb
|
3
|
+
# $Id: og.rb 259 2005-02-15 08:54:54Z gmosx $
|
4
4
|
|
5
|
+
require 'glue'
|
5
6
|
require 'glue/logger'
|
6
7
|
require 'glue/attribute'
|
7
8
|
require 'glue/property'
|
@@ -9,6 +10,7 @@ require 'glue/array'
|
|
9
10
|
require 'glue/hash'
|
10
11
|
require 'glue/time'
|
11
12
|
require 'glue/pool'
|
13
|
+
require 'glue/validation'
|
12
14
|
|
13
15
|
# Og (ObjectGraph) is an efficient, yet simple Object-Relational
|
14
16
|
# mapping library.
|
@@ -19,10 +21,10 @@ require 'glue/pool'
|
|
19
21
|
#
|
20
22
|
# + Object-Relational mapping.
|
21
23
|
# + Absolutely no configuration files.
|
22
|
-
# + Multiple backends (PostgreSQL, MySQL).
|
24
|
+
# + Multiple backends (PostgreSQL, MySQL, SQLite).
|
23
25
|
# + ActiveRecord-style meta language and db aware methods.
|
24
|
-
# + Deserialize to Ruby Objects
|
25
|
-
# + Deserialize sql join queries to Ruby Objects.
|
26
|
+
# + Deserialize to Ruby Objects.
|
27
|
+
# + Deserialize sql join queries to Ruby Objects (temporarily dissabled).
|
26
28
|
# + Serialize arbitrary ruby object graphs through YAML.
|
27
29
|
# + Connection pooling.
|
28
30
|
# + Thread safety.
|
@@ -89,6 +91,18 @@ require 'glue/pool'
|
|
89
91
|
|
90
92
|
class Og
|
91
93
|
|
94
|
+
# The name.
|
95
|
+
|
96
|
+
Name = 'ObjectGraph'
|
97
|
+
|
98
|
+
# The version.
|
99
|
+
|
100
|
+
Version = '0.10.0'
|
101
|
+
|
102
|
+
# Library path.
|
103
|
+
|
104
|
+
LibPath = File.dirname(__FILE__)
|
105
|
+
|
92
106
|
# If true, only allow reading from the database. Usefull
|
93
107
|
# for maintainance.
|
94
108
|
|
@@ -131,267 +145,19 @@ class Og
|
|
131
145
|
@@db.get_connection
|
132
146
|
end
|
133
147
|
|
134
|
-
|
135
|
-
|
136
|
-
# gmosx: leave this here.
|
137
|
-
require 'og/enchant'
|
138
|
-
require 'og/meta'
|
139
|
-
|
140
|
-
class Og
|
141
|
-
|
142
|
-
# Marker module. If included this in a class, the Og automanager
|
143
|
-
# ignores this class.
|
148
|
+
# The adapter of the active database.
|
144
149
|
|
145
|
-
|
146
|
-
|
147
|
-
# Encapsulates an Og Database.
|
148
|
-
|
149
|
-
class Database
|
150
|
-
include Og::Enchant
|
151
|
-
|
152
|
-
# Managed class metadata
|
153
|
-
|
154
|
-
class ManagedClassMeta
|
155
|
-
# The managed class.
|
156
|
-
attr_accessor :klass
|
157
|
-
|
158
|
-
# A mapping of the database fields to the object properties.
|
159
|
-
attr_accessor :field_index
|
160
|
-
|
161
|
-
def initialize(klass = nil)
|
162
|
-
@klass = klass
|
163
|
-
@field_index = {}
|
164
|
-
end
|
150
|
+
def self.adapter
|
151
|
+
@@db.adapter
|
165
152
|
end
|
166
153
|
|
167
|
-
#
|
154
|
+
# Marker module. If included this in a class, the Og automanager
|
155
|
+
# ignores this class.
|
168
156
|
|
169
|
-
|
157
|
+
module Unmanageable; end
|
170
158
|
|
171
|
-
# Pool of connections to the backend.
|
172
|
-
|
173
|
-
attr_accessor :connection_pool
|
174
|
-
|
175
|
-
# Managed classes.
|
176
|
-
|
177
|
-
attr_accessor :managed_classes
|
178
|
-
|
179
|
-
# Initialize the database interface.
|
180
|
-
|
181
|
-
def initialize(config)
|
182
|
-
@config = config
|
183
|
-
|
184
|
-
# populate with default options if needed.
|
185
|
-
@config[:connection_count] ||= 1
|
186
|
-
|
187
|
-
# require the backend.
|
188
|
-
backend = @config[:backend] || "psql"
|
189
|
-
require "og/backends/#{backend}"
|
190
|
-
eval %{ @config[:backend] = #{backend.capitalize}Backend }
|
191
|
-
|
192
|
-
@connection_pool = N::Pool.new
|
193
|
-
@managed_classes = N::SafeHash.new
|
194
|
-
|
195
|
-
Logger.info "Connecting to database '#{@config[:database]}' using backend '#{backend}'."
|
196
|
-
|
197
|
-
@config[:connection_count].times do
|
198
|
-
@connection_pool << Og::Connection.new(self)
|
199
|
-
end
|
200
|
-
|
201
|
-
# gmosx, FIXME: this automanage code is not elegant and slow
|
202
|
-
# should probably recode this, along with glue/property.rb
|
203
|
-
|
204
|
-
if Og.auto_manage_classes
|
205
|
-
# automatically manage classes with properties and metadata.
|
206
|
-
# gmosx: Any idea how to optimize this?
|
207
|
-
classes_to_manage = []
|
208
|
-
ObjectSpace.each_object(Class) do |c|
|
209
|
-
if c.respond_to?(:__props) and c.__props
|
210
|
-
classes_to_manage << c
|
211
|
-
end
|
212
|
-
end
|
213
|
-
Logger.info "Og auto manages the following classes:"
|
214
|
-
Logger.info "#{classes_to_manage.inspect}"
|
215
|
-
manage_classes(*classes_to_manage)
|
216
|
-
end
|
217
|
-
|
218
|
-
# use the newly created database.
|
219
|
-
Og.use(self)
|
220
|
-
end
|
221
|
-
|
222
|
-
# Shutdown the database interface.
|
223
|
-
|
224
|
-
def shutdown
|
225
|
-
for con in @connection_pool
|
226
|
-
con.close()
|
227
|
-
end
|
228
|
-
end
|
229
|
-
alias_method :close, :shutdown
|
230
|
-
|
231
|
-
# Get a connection from the pool to access the database.
|
232
|
-
# Stores the connection in a thread-local variable.
|
233
|
-
|
234
|
-
def get_connection
|
235
|
-
thread = Thread.current
|
236
|
-
|
237
|
-
unless conn = thread[:og_conn]
|
238
|
-
conn = @connection_pool.pop()
|
239
|
-
thread[:og_conn] = conn
|
240
|
-
end
|
241
|
-
|
242
|
-
return conn
|
243
|
-
end
|
244
|
-
alias_method :connection, :get_connection
|
245
|
-
|
246
|
-
# Restore an unused connection to the pool.
|
247
|
-
|
248
|
-
def put_connection
|
249
|
-
thread = Thread.current
|
250
|
-
|
251
|
-
if conn = thread[:og_conn]
|
252
|
-
thread[:og_conn] = nil
|
253
|
-
return @connection_pool.push(conn)
|
254
|
-
end
|
255
|
-
end
|
256
|
-
|
257
|
-
# Utility method, automatically restores a connection to the pool.
|
258
|
-
|
259
|
-
def connect(deserialize = nil, &block)
|
260
|
-
result = nil
|
261
|
-
|
262
|
-
begin
|
263
|
-
conn = get_connection()
|
264
|
-
conn.deserialize = deserialize unless deserialize.nil?
|
265
|
-
|
266
|
-
result = yield(conn)
|
267
|
-
|
268
|
-
conn.deserialize = true
|
269
|
-
ensure
|
270
|
-
put_connection()
|
271
|
-
end
|
272
|
-
|
273
|
-
return result
|
274
|
-
end
|
275
|
-
alias_method :open, :connect
|
276
|
-
|
277
|
-
|
278
|
-
# Register a standard Ruby class as managed.
|
279
|
-
|
280
|
-
def manage(klass)
|
281
|
-
return if managed?(klass) or klass.ancestors.include?(Og::Unmanageable)
|
282
|
-
|
283
|
-
@managed_classes[klass] = ManagedClassMeta.new(klass)
|
284
|
-
|
285
|
-
# Add standard og methods to the class.
|
286
|
-
convert(klass)
|
287
|
-
|
288
|
-
# Add helper methods to the class.
|
289
|
-
enchant(klass) if Og.enchant_managed_classes
|
290
|
-
end
|
291
|
-
|
292
|
-
# Helper method to set multiple managed classes.
|
293
|
-
|
294
|
-
def manage_classes(*klasses)
|
295
|
-
for klass in klasses
|
296
|
-
manage(klass)
|
297
|
-
end
|
298
|
-
end
|
299
|
-
|
300
|
-
# Stop managing a Ruby class
|
301
|
-
|
302
|
-
def unmanage(klass)
|
303
|
-
@managed_classes.delete(klass)
|
304
|
-
end
|
305
|
-
|
306
|
-
# Is this class managed?
|
307
|
-
#
|
308
|
-
def managed?(klass)
|
309
|
-
return @managed_classes.include?(klass)
|
310
|
-
end
|
311
|
-
|
312
|
-
# Add standard og functionality to the class
|
313
|
-
|
314
|
-
def convert(klass)
|
315
|
-
# Grab backend class
|
316
|
-
backend = @config[:backend]
|
317
|
-
|
318
|
-
# gmosx: this check is needed to allow the developer to customize
|
319
|
-
# the sql generated for oid
|
320
|
-
backend.eval_og_oid(klass) unless klass.instance_methods.include?(:oid)
|
321
|
-
|
322
|
-
klass.class_eval %{
|
323
|
-
DBTABLE = "#{backend.table(klass)}"
|
324
|
-
DBSEQ = "#{backend.table(klass)}_oids_seq"
|
325
|
-
|
326
|
-
def to_i()
|
327
|
-
@oid
|
328
|
-
end
|
329
|
-
}
|
330
|
-
|
331
|
-
# Create the schema for this class if not available.
|
332
|
-
create_table(klass)
|
333
|
-
|
334
|
-
# Precompile some code that gets executed all the time.
|
335
|
-
# Deletion code is not precompiled, because it is not used
|
336
|
-
# as frequently.
|
337
|
-
backend.eval_og_insert(klass)
|
338
|
-
backend.eval_og_update(klass)
|
339
|
-
backend.eval_og_deserialize(klass, self)
|
340
|
-
end
|
341
|
-
|
342
|
-
# Automatically wrap connection methods.
|
343
|
-
#
|
344
|
-
def self.wrap_method(method, args)
|
345
|
-
args = args.split(/,/)
|
346
|
-
class_eval %{
|
347
|
-
def #{method}(#{args.join(", ")})
|
348
|
-
thread = Thread.current
|
349
|
-
|
350
|
-
unless conn = thread[:og_conn]
|
351
|
-
conn = @connection_pool.pop()
|
352
|
-
thread[:og_conn] = conn
|
353
|
-
end
|
354
|
-
|
355
|
-
return conn.#{method}(#{args.collect {|a| a.split(/=/)[0]}.join(", ")})
|
356
|
-
end
|
357
|
-
}
|
358
|
-
end
|
359
|
-
|
360
|
-
wrap_method :create_table, "klass"
|
361
|
-
wrap_method :drop_table, "klass"
|
362
|
-
wrap_method :save, "obj"; alias_method :<<, :save; alias_method :put, :save
|
363
|
-
wrap_method :insert, "obj"
|
364
|
-
wrap_method :update, "obj"
|
365
|
-
wrap_method :update_properties, "update_sql, obj_or_oid, klass = nil"
|
366
|
-
wrap_method :pupdate, "update_sql, obj_or_oid, klass = nil"
|
367
|
-
wrap_method :load, "oid, klass"; alias_method :get, :load
|
368
|
-
wrap_method :load_by_oid, "oid, klass"
|
369
|
-
wrap_method :load_by_name, "name, klass"
|
370
|
-
wrap_method :load_all, "klass, extrasql = nil"
|
371
|
-
wrap_method :select, "sql, klass"
|
372
|
-
wrap_method :select_one, "sql, klass"
|
373
|
-
wrap_method :count, "sql, klass = nil"
|
374
|
-
wrap_method :delete, "obj_or_oid, klass = nil"
|
375
|
-
wrap_method :query, "sql"
|
376
|
-
wrap_method :exec, "sql"
|
377
|
-
|
378
|
-
class << self
|
379
|
-
def create_db!(config)
|
380
|
-
get_connection().db.create_db(config[:database], config[:user],
|
381
|
-
config[:password])
|
382
|
-
end
|
383
|
-
alias_method :create!, :create_db!
|
384
|
-
|
385
|
-
def drop_db!(config)
|
386
|
-
backend = config[:backend] || "psql"
|
387
|
-
require "og/backends/#{backend}"
|
388
|
-
eval %{
|
389
|
-
#{backend.capitalize}Backend.drop_db(config[:database], config[:user],
|
390
|
-
config[:password])
|
391
|
-
}
|
392
|
-
end
|
393
|
-
alias_method :drop!, :drop_db!
|
394
|
-
end
|
395
159
|
end
|
396
160
|
|
397
|
-
|
161
|
+
# gmosx: leave this here.
|
162
|
+
|
163
|
+
require 'og/database'
|
data/lib/og/adapter.rb
ADDED
@@ -0,0 +1,352 @@
|
|
1
|
+
# * George Moschovitis <gm@navel.gr>
|
2
|
+
# (c) 2004-2005 Navel, all rights reserved.
|
3
|
+
# $Id: adapter.rb 255 2005-02-10 12:45:32Z gmosx $
|
4
|
+
|
5
|
+
require 'yaml'
|
6
|
+
require 'singleton'
|
7
|
+
|
8
|
+
require 'og/connection'
|
9
|
+
|
10
|
+
class Og
|
11
|
+
|
12
|
+
# An adapter communicates with the backend datastore.
|
13
|
+
# The adapters for all supported datastores extend this
|
14
|
+
# class. Typically, an RDBMS is used to implement a
|
15
|
+
# datastore.
|
16
|
+
|
17
|
+
class Adapter
|
18
|
+
include Singleton
|
19
|
+
|
20
|
+
# A mapping between Ruby and backend Datastore types.
|
21
|
+
|
22
|
+
attr_accessor :typemap
|
23
|
+
|
24
|
+
# Lookup the adapter instance from the adapter name.
|
25
|
+
|
26
|
+
def self.for_name(name)
|
27
|
+
require "og/adapters/#{name}"
|
28
|
+
eval %{ return #{name.capitalize}Adapter.instance }
|
29
|
+
end
|
30
|
+
|
31
|
+
def initialize
|
32
|
+
@typemap = {
|
33
|
+
Integer => 'integer',
|
34
|
+
Fixnum => 'integer',
|
35
|
+
Float => 'float',
|
36
|
+
String => 'text',
|
37
|
+
Time => 'timestamp',
|
38
|
+
Date => 'date',
|
39
|
+
TrueClass => 'boolean',
|
40
|
+
Object => 'text',
|
41
|
+
Array => 'text',
|
42
|
+
Hash => 'text'
|
43
|
+
}
|
44
|
+
end
|
45
|
+
|
46
|
+
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
47
|
+
# :section: Utilities
|
48
|
+
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
49
|
+
|
50
|
+
# Escape an SQL string
|
51
|
+
|
52
|
+
def self.escape(str)
|
53
|
+
return nil unless str
|
54
|
+
return str.gsub( /'/, "''" )
|
55
|
+
end
|
56
|
+
|
57
|
+
# Convert a ruby time to an sql timestamp.
|
58
|
+
# TODO: Optimize this
|
59
|
+
|
60
|
+
def self.timestamp(time = Time.now)
|
61
|
+
return nil unless time
|
62
|
+
return time.strftime("%Y-%m-%d %H:%M:%S")
|
63
|
+
end
|
64
|
+
|
65
|
+
# Output YYY-mm-dd
|
66
|
+
# TODO: Optimize this
|
67
|
+
|
68
|
+
def self.date(date)
|
69
|
+
return nil unless date
|
70
|
+
return "#{date.year}-#{date.month}-#{date.mday}"
|
71
|
+
end
|
72
|
+
|
73
|
+
# Parse sql datetime
|
74
|
+
# TODO: Optimize this
|
75
|
+
|
76
|
+
def self.parse_timestamp(str)
|
77
|
+
return Time.parse(str)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Input YYYY-mm-dd
|
81
|
+
# TODO: Optimize this
|
82
|
+
|
83
|
+
def self.parse_date(str)
|
84
|
+
return nil unless str
|
85
|
+
return Date.strptime(str)
|
86
|
+
end
|
87
|
+
|
88
|
+
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
89
|
+
# :section: Database methods
|
90
|
+
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
91
|
+
|
92
|
+
# Create the database.
|
93
|
+
|
94
|
+
def create_db(database, user = nil, password = nil)
|
95
|
+
Logger.info "Creating database '#{database}'."
|
96
|
+
end
|
97
|
+
|
98
|
+
# Drop the database.
|
99
|
+
|
100
|
+
def drop_db(database, user = nil, password = nil)
|
101
|
+
Logger.info "Dropping database '#{database}'."
|
102
|
+
end
|
103
|
+
|
104
|
+
# Create a new connection to the backend.
|
105
|
+
|
106
|
+
def new_connection(db)
|
107
|
+
return Og::Connection.new(db)
|
108
|
+
end
|
109
|
+
|
110
|
+
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
111
|
+
# :section: OR mapping methods and utilities.
|
112
|
+
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
113
|
+
|
114
|
+
# Encode the name of the klass as an sql safe string.
|
115
|
+
# The Module separators are replaced with _ and NOT stripped
|
116
|
+
# out so that we can convert back to the original notation if
|
117
|
+
# needed. The leading module if available is removed.
|
118
|
+
|
119
|
+
def self.encode(klass)
|
120
|
+
"#{klass.name.gsub(/^.*::/, "")}".gsub(/::/, "_").downcase
|
121
|
+
end
|
122
|
+
|
123
|
+
# The name of the SQL table where objects of this class
|
124
|
+
# are stored.
|
125
|
+
|
126
|
+
def self.table(klass)
|
127
|
+
"_#{Og.table_prefix}#{encode(klass)}"
|
128
|
+
end
|
129
|
+
|
130
|
+
# The name of the join table for the two given classes.
|
131
|
+
|
132
|
+
def self.join_table(klass1, klass2)
|
133
|
+
"_#{Og.table_prefix}j_#{encode(klass1)}_#{encode(klass2)}"
|
134
|
+
end
|
135
|
+
|
136
|
+
# Return an sql string evaluator for the property.
|
137
|
+
# No need to optimize this, used only to precalculate code.
|
138
|
+
# YAML is used to store general Ruby objects to be more
|
139
|
+
# portable.
|
140
|
+
#
|
141
|
+
# FIXME: add extra handling for float.
|
142
|
+
|
143
|
+
def write_prop(p)
|
144
|
+
if p.klass.ancestors.include?(Integer)
|
145
|
+
return "#\{@#{p.symbol} || 'NULL'\}"
|
146
|
+
elsif p.klass.ancestors.include?(Float)
|
147
|
+
return "#\{@#{p.symbol} || 'NULL'\}"
|
148
|
+
elsif p.klass.ancestors.include?(String)
|
149
|
+
return "'#\{#{self.class}.escape(@#{p.symbol})\}'"
|
150
|
+
elsif p.klass.ancestors.include?(Time)
|
151
|
+
return %|#\{@#{p.symbol} ? "'#\{#{self.class}.timestamp(@#{p.symbol})\}'" : 'NULL'\}|
|
152
|
+
elsif p.klass.ancestors.include?(Date)
|
153
|
+
return %|#\{@#{p.symbol} ? "'#\{#{self.class}.date(@#{p.symbol})\}'" : 'NULL'\}|
|
154
|
+
elsif p.klass.ancestors.include?(TrueClass)
|
155
|
+
return "#\{@#{p.symbol} ? \"'t'\" : 'NULL' \}"
|
156
|
+
else
|
157
|
+
return %|#\{@#{p.symbol} ? "'#\{#{self.class}.escape(@#{p.symbol}.to_yaml)\}'" : "''"\}|
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
# Return an evaluator for reading the property.
|
162
|
+
# No need to optimize this, used only to precalculate code.
|
163
|
+
|
164
|
+
def read_prop(p, idx)
|
165
|
+
if p.klass.ancestors.include?(Integer)
|
166
|
+
return "res[#{idx}].to_i"
|
167
|
+
elsif p.klass.ancestors.include?(Float)
|
168
|
+
return "res[#{idx}].to_f"
|
169
|
+
elsif p.klass.ancestors.include?(String)
|
170
|
+
return "res[#{idx}]"
|
171
|
+
elsif p.klass.ancestors.include?(Time)
|
172
|
+
return "#{self.class}.parse_timestamp(res[#{idx}])"
|
173
|
+
elsif p.klass.ancestors.include?(Date)
|
174
|
+
return "#{self.class}.parse_date(res[#{idx}])"
|
175
|
+
elsif p.klass.ancestors.include?(TrueClass)
|
176
|
+
return "('0' != res[#{idx}])"
|
177
|
+
else
|
178
|
+
return "YAML::load(res[#{idx}])"
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
# Create the fields that correpsond to the klass properties.
|
183
|
+
# The generated fields array is used in create_table.
|
184
|
+
# If the property has an :sql metadata this overrides the
|
185
|
+
# default mapping. If the property has an :extra_sql metadata
|
186
|
+
# the extra sql is appended after the default mapping.
|
187
|
+
|
188
|
+
def create_fields(klass)
|
189
|
+
fields = []
|
190
|
+
|
191
|
+
klass.__props.each do |p|
|
192
|
+
klass.sql_index(p.symbol) if p.meta[:sql_index]
|
193
|
+
|
194
|
+
field = "#{p.symbol}"
|
195
|
+
|
196
|
+
if p.meta and p.meta[:sql]
|
197
|
+
field << " #{p.meta[:sql]}"
|
198
|
+
else
|
199
|
+
field << " #{@typemap[p.klass]}"
|
200
|
+
# attach extra sql
|
201
|
+
if p.meta and extra_sql = p.meta[:extra_sql]
|
202
|
+
field << " #{extra_sql}"
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
fields << field
|
207
|
+
end
|
208
|
+
|
209
|
+
return fields
|
210
|
+
end
|
211
|
+
|
212
|
+
# Create the managed object table. The properties of the
|
213
|
+
# object are mapped to the table columns. Additional sql relations
|
214
|
+
# and constrains are created (indicices, sequences, etc).
|
215
|
+
|
216
|
+
def create_table(klass)
|
217
|
+
raise 'Not implemented!'
|
218
|
+
end
|
219
|
+
|
220
|
+
# Returns the props that will be included in the insert query.
|
221
|
+
# For some backends the oid should be stripped.
|
222
|
+
|
223
|
+
def props_for_insert(klass)
|
224
|
+
klass.__props
|
225
|
+
end
|
226
|
+
|
227
|
+
# Returns the code that actually inserts the object into the
|
228
|
+
# database. Returns the code as String.
|
229
|
+
|
230
|
+
def insert_code(klass, sql, pre_cb, post_cb)
|
231
|
+
raise 'Not implemented!'
|
232
|
+
end
|
233
|
+
|
234
|
+
# Generate the mapping of the database fields to the
|
235
|
+
# object properties.
|
236
|
+
|
237
|
+
def calc_field_index(klass, og)
|
238
|
+
raise 'Not implemented!'
|
239
|
+
end
|
240
|
+
|
241
|
+
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
242
|
+
# :section: Managed object enchant methods
|
243
|
+
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
244
|
+
|
245
|
+
# Generate the property for oid.
|
246
|
+
|
247
|
+
def eval_og_oid(klass)
|
248
|
+
klass.class_eval %{
|
249
|
+
prop_accessor :oid, Fixnum, :sql => "integer PRIMARY KEY"
|
250
|
+
}
|
251
|
+
end
|
252
|
+
|
253
|
+
# Precompile the insert code for the given class.
|
254
|
+
# The generated code sets the oid when inserting!
|
255
|
+
|
256
|
+
def eval_og_insert(klass, db)
|
257
|
+
if klass.instance_methods.include?('og_pre_insert')
|
258
|
+
pre_cb = 'og_pre_insert(conn);'
|
259
|
+
else
|
260
|
+
pre_cb = ''
|
261
|
+
end
|
262
|
+
|
263
|
+
if klass.instance_methods.include?('og_post_insert')
|
264
|
+
post_cb = 'og_post_insert(conn);'
|
265
|
+
else
|
266
|
+
post_cb = ''
|
267
|
+
end
|
268
|
+
|
269
|
+
if klass.instance_methods.include?('og_pre_insert_update')
|
270
|
+
pre_cb << 'og_pre_insert_update(conn);'
|
271
|
+
end
|
272
|
+
|
273
|
+
if klass.instance_methods.include?('og_post_insert_update')
|
274
|
+
post_cb << 'og_post_insert_update(conn);'
|
275
|
+
end
|
276
|
+
|
277
|
+
klass.class_eval %{
|
278
|
+
def og_insert(conn)
|
279
|
+
#{insert_code(klass, db, pre_cb, post_cb)}
|
280
|
+
end
|
281
|
+
}
|
282
|
+
end
|
283
|
+
|
284
|
+
# Precompile the update code for the given class.
|
285
|
+
# Ignore the oid when updating!
|
286
|
+
|
287
|
+
def eval_og_update(klass, db)
|
288
|
+
props = klass.__props.reject { |p| :oid == p.symbol }
|
289
|
+
|
290
|
+
updates = props.collect { |p|
|
291
|
+
"#{p.name}=#{write_prop(p)}"
|
292
|
+
}
|
293
|
+
|
294
|
+
sql = "UPDATE #{klass::DBTABLE} SET #{updates.join(', ')} WHERE oid=#\{@oid\}"
|
295
|
+
|
296
|
+
if klass.instance_methods.include?('og_pre_update')
|
297
|
+
pre_cb = 'og_pre_update(conn);'
|
298
|
+
else
|
299
|
+
pre_cb = ''
|
300
|
+
end
|
301
|
+
|
302
|
+
if klass.instance_methods.include?('og_post_update')
|
303
|
+
post_cb = 'og_post_update(conn);'
|
304
|
+
else
|
305
|
+
post_cb = ''
|
306
|
+
end
|
307
|
+
|
308
|
+
if klass.instance_methods.include?('og_pre_insert_update')
|
309
|
+
pre_cb << 'og_pre_insert_update(conn);'
|
310
|
+
end
|
311
|
+
|
312
|
+
if klass.instance_methods.include?('og_post_insert_update')
|
313
|
+
post_cb << 'og_post_insert_update(conn);'
|
314
|
+
end
|
315
|
+
|
316
|
+
klass.class_eval %{
|
317
|
+
def og_update(conn)
|
318
|
+
#{pre_cb}
|
319
|
+
conn.exec "#{sql}"
|
320
|
+
#{post_cb}
|
321
|
+
end
|
322
|
+
}
|
323
|
+
end
|
324
|
+
|
325
|
+
# Precompile the code to read (deserialize) objects of the
|
326
|
+
# given class from the backend. In order to allow for changing
|
327
|
+
# field/attribute orders we have to use a field mapping hash.
|
328
|
+
|
329
|
+
def eval_og_read(klass, db)
|
330
|
+
calc_field_index(klass, db)
|
331
|
+
|
332
|
+
props = klass.__props
|
333
|
+
code = []
|
334
|
+
|
335
|
+
props.each do |p|
|
336
|
+
if idx = db.managed_classes[klass].field_index[p.name]
|
337
|
+
# more fault tolerant if a new field is added and it
|
338
|
+
# doesnt exist in the database.
|
339
|
+
code << "@#{p.name} = #{read_prop(p, idx)}"
|
340
|
+
end
|
341
|
+
end
|
342
|
+
|
343
|
+
klass.class_eval %{
|
344
|
+
def og_read(res, tuple = nil)
|
345
|
+
#{code.join('; ')}
|
346
|
+
end
|
347
|
+
}
|
348
|
+
end
|
349
|
+
|
350
|
+
end
|
351
|
+
|
352
|
+
end
|