og 0.18.1 → 0.19.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +123 -0
- data/README +13 -2
- data/doc/AUTHORS +4 -1
- data/doc/RELEASES +72 -1
- data/examples/run.rb +3 -3
- data/lib/og.rb +8 -1
- data/lib/og/collection.rb +3 -0
- data/lib/og/entity.rb +27 -7
- data/lib/og/manager.rb +46 -3
- data/lib/og/mixin/optimistic_locking.rb +59 -0
- data/lib/og/relation.rb +53 -4
- data/lib/og/relation/has_many.rb +16 -0
- data/lib/og/store.rb +4 -4
- data/lib/og/store/mysql.rb +19 -5
- data/lib/og/store/psql.rb +39 -0
- data/lib/og/store/sql.rb +165 -47
- data/lib/og/store/sqlite.rb +21 -5
- data/test/og/mixin/tc_optimistic_locking.rb +56 -0
- data/test/og/tc_inheritance.rb +103 -0
- data/test/og/tc_polymorphic.rb +64 -0
- data/test/og/tc_store.rb +44 -10
- metadata +7 -4
- data/lib/og/store/madeleine.rb +0 -2
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'facet/macro'
|
2
|
+
|
3
|
+
module Og
|
4
|
+
|
5
|
+
# This error is thrown when you the object you are trynig
|
6
|
+
# to update is allready updated by another thread.
|
7
|
+
|
8
|
+
class StaleObjectError < StandardError
|
9
|
+
end
|
10
|
+
|
11
|
+
# Include this module into entity classes to provide optimistic
|
12
|
+
# locking suport. For more information on optimistic locking
|
13
|
+
# please consult:
|
14
|
+
#
|
15
|
+
# http://c2.com/cgi/wiki?OptimisticLocking
|
16
|
+
# http://en.wikipedia.org/wiki/Optimistic_concurrency_control
|
17
|
+
|
18
|
+
module Locking
|
19
|
+
property :lock_version, Fixnum, :default => 0
|
20
|
+
pre "@lock_version = 0", :on => :og_insert
|
21
|
+
|
22
|
+
def self.append_features(base) #:nodoc:
|
23
|
+
PropertyUtils.copy_features(self, base)
|
24
|
+
|
25
|
+
super
|
26
|
+
|
27
|
+
base.module_eval do
|
28
|
+
def self.enchant
|
29
|
+
self.send :alias_method, :update_without_lock, :update
|
30
|
+
self.send :alias_method, :update, :update_with_lock
|
31
|
+
self.send :alias_method, :save_without_lock, :save
|
32
|
+
self.send :alias_method, :save, :save_with_lock
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def update_with_lock
|
38
|
+
lock = @lock_version
|
39
|
+
@lock_version += 1
|
40
|
+
|
41
|
+
unless update_without_lock(:condition => "lock_version=#{lock}") == 1
|
42
|
+
raise(StaleObjectError, 'Attempted to update a stale object')
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def save_with_lock
|
47
|
+
lock = @lock_version
|
48
|
+
@lock_version += 1
|
49
|
+
|
50
|
+
unless save_without_lock(:condition => "lock_version=#{lock}") == 1
|
51
|
+
raise(StaleObjectError, 'Attempted to update a stale object')
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
# * George Moschovitis <gm@navel.gr>
|
data/lib/og/relation.rb
CHANGED
@@ -11,6 +11,8 @@ class Relation
|
|
11
11
|
|
12
12
|
attr_accessor :options
|
13
13
|
|
14
|
+
attr_accessor :is_polymorphic
|
15
|
+
|
14
16
|
# A generalized initialize method for all relations.
|
15
17
|
# Contains common setup code.
|
16
18
|
|
@@ -24,6 +26,27 @@ class Relation
|
|
24
26
|
|
25
27
|
@options[:target_class] = args.pop
|
26
28
|
|
29
|
+
if target_class == Object
|
30
|
+
# If the target class is just an Object mark that class
|
31
|
+
# as a polymorphic parent class.
|
32
|
+
# This class acts as template
|
33
|
+
# to generate customized versions of this class.
|
34
|
+
|
35
|
+
owner_class.meta(:polymorphic, owner_class)
|
36
|
+
elsif target_class.respond_to?(:metadata) and target_class.metadata.polymorphic
|
37
|
+
# If the target class is polymorphic, create a specialized
|
38
|
+
# version of the class enclosed in the owner namespace.
|
39
|
+
|
40
|
+
target_dm = target_class.to_s.demodulize
|
41
|
+
owner_class.module_eval %{
|
42
|
+
class #{owner_class}::#{target_dm} < #{target_class}
|
43
|
+
end
|
44
|
+
}
|
45
|
+
eval %{
|
46
|
+
@options[:target_class] = #{owner_class}::#{target_dm}
|
47
|
+
}
|
48
|
+
end
|
49
|
+
|
27
50
|
target_name = if collection
|
28
51
|
:target_plural_name
|
29
52
|
else
|
@@ -53,9 +76,21 @@ class Relation
|
|
53
76
|
@options[key] = val
|
54
77
|
end
|
55
78
|
|
79
|
+
# Is the relation polymorphic?
|
80
|
+
|
81
|
+
def polymorphic?
|
82
|
+
target_class == Object
|
83
|
+
end
|
84
|
+
|
85
|
+
#--
|
86
|
+
# gmosx, TODO: remove, this is not really needed.
|
87
|
+
#++
|
88
|
+
|
56
89
|
def resolve_options
|
57
90
|
@options[:owner_pk], @options[:owner_pkclass] = owner_class.primary_key
|
58
|
-
|
91
|
+
if target_class.respond_to?(:primary_key)
|
92
|
+
@options[:target_pk], @options[:target_pkclass] = target_class.primary_key
|
93
|
+
end
|
59
94
|
end
|
60
95
|
|
61
96
|
# To avoid forward declarations, references to undefined
|
@@ -81,6 +116,14 @@ class Relation
|
|
81
116
|
@options[:target_class] = klass
|
82
117
|
end
|
83
118
|
end
|
119
|
+
|
120
|
+
# Resolve a polymorphic target class.
|
121
|
+
# Overrided in subclasses.
|
122
|
+
|
123
|
+
def resolve_polymorphic
|
124
|
+
end
|
125
|
+
|
126
|
+
# This method is implemented in subclasses.
|
84
127
|
|
85
128
|
def enchant
|
86
129
|
end
|
@@ -93,12 +136,18 @@ class Relation
|
|
93
136
|
|
94
137
|
class << self
|
95
138
|
|
139
|
+
def resolve(klass, action)
|
140
|
+
if klass.__meta[:relations]
|
141
|
+
for relation in klass.__meta[:relations]
|
142
|
+
relation.send(action)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
96
147
|
def enchant(klass)
|
97
148
|
if klass.__meta[:relations]
|
98
149
|
for relation in klass.__meta[:relations]
|
99
|
-
relation.
|
100
|
-
relation.resolve_options
|
101
|
-
relation.enchant
|
150
|
+
relation.enchant unless relation.target_class == Object
|
102
151
|
end
|
103
152
|
end
|
104
153
|
end
|
data/lib/og/relation/has_many.rb
CHANGED
@@ -19,6 +19,22 @@ end
|
|
19
19
|
# article.comments.size
|
20
20
|
|
21
21
|
class HasMany < Relation
|
22
|
+
=begin
|
23
|
+
def initialize(args, options = {})
|
24
|
+
super
|
25
|
+
|
26
|
+
# TODO: clean this up.
|
27
|
+
|
28
|
+
unless target_class.relations.find { |r| r.is_a?(BelongsTo) and r.target_class == owner_class }
|
29
|
+
target_class.belongs_to(owner_class)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
=end
|
33
|
+
def resolve_polymorphic
|
34
|
+
unless target_class.relations.find { |r| r.is_a?(BelongsTo) and r.target_class == owner_class }
|
35
|
+
target_class.belongs_to(owner_class)
|
36
|
+
end
|
37
|
+
end
|
22
38
|
|
23
39
|
def enchant
|
24
40
|
self[:owner_singular_name] = owner_class.to_s.demodulize.underscore.downcase
|
data/lib/og/store.rb
CHANGED
@@ -112,9 +112,9 @@ class Store
|
|
112
112
|
# Save an object to store. Insert if this is a new object or
|
113
113
|
# update if this is already inserted in the database.
|
114
114
|
|
115
|
-
def save(obj)
|
115
|
+
def save(obj, options = nil)
|
116
116
|
if obj.saved?
|
117
|
-
obj.og_update(self)
|
117
|
+
obj.og_update(self, options)
|
118
118
|
else
|
119
119
|
obj.og_insert(self)
|
120
120
|
end
|
@@ -129,8 +129,8 @@ class Store
|
|
129
129
|
|
130
130
|
# Update an object in the store.
|
131
131
|
|
132
|
-
def update(obj,
|
133
|
-
obj.og_update(self)
|
132
|
+
def update(obj, options = nil)
|
133
|
+
obj.og_update(self, options)
|
134
134
|
end
|
135
135
|
|
136
136
|
# Update selected properties of an object or class of
|
data/lib/og/store/mysql.rb
CHANGED
@@ -115,7 +115,7 @@ class MysqlStore < SqlStore
|
|
115
115
|
end
|
116
116
|
|
117
117
|
def query(sql)
|
118
|
-
|
118
|
+
Logger.debug sql if $DBG
|
119
119
|
@conn.query_with_result = true
|
120
120
|
return @conn.query(sql)
|
121
121
|
rescue => ex
|
@@ -123,7 +123,7 @@ class MysqlStore < SqlStore
|
|
123
123
|
end
|
124
124
|
|
125
125
|
def exec(sql)
|
126
|
-
|
126
|
+
Logger.debug sql if $DBG
|
127
127
|
@conn.query_with_result = false
|
128
128
|
@conn.query(sql)
|
129
129
|
rescue => ex
|
@@ -149,6 +149,11 @@ class MysqlStore < SqlStore
|
|
149
149
|
# FIXME: InnoDB supports transactions.
|
150
150
|
end
|
151
151
|
|
152
|
+
def sql_update(sql)
|
153
|
+
exec(sql)
|
154
|
+
@conn.affected_rows
|
155
|
+
end
|
156
|
+
|
152
157
|
private
|
153
158
|
|
154
159
|
def create_table(klass)
|
@@ -161,8 +166,12 @@ private
|
|
161
166
|
if klass.__meta and constrains = klass.__meta[:sql_constrain]
|
162
167
|
sql << ", #{constrains.join(', ')}"
|
163
168
|
end
|
164
|
-
|
165
|
-
|
169
|
+
|
170
|
+
if table_type = @options[:table_type]
|
171
|
+
sql << ") TYPE = #{table_type};"
|
172
|
+
else
|
173
|
+
sql << ");"
|
174
|
+
end
|
166
175
|
|
167
176
|
# Create indices.
|
168
177
|
|
@@ -262,9 +271,14 @@ private
|
|
262
271
|
end
|
263
272
|
|
264
273
|
def eval_og_insert(klass)
|
265
|
-
props = klass.properties
|
274
|
+
props = klass.properties.dup
|
266
275
|
values = props.collect { |p| write_prop(p) }.join(',')
|
267
276
|
|
277
|
+
if klass.metadata.superclass or klass.metadata.subclasses
|
278
|
+
props << Property.new(:ogtype, String)
|
279
|
+
values << ", '#{klass}'"
|
280
|
+
end
|
281
|
+
|
268
282
|
sql = "INSERT INTO #{klass::OGTABLE} (#{props.collect {|p| p.symbol.to_s}.join(',')}) VALUES (#{values})"
|
269
283
|
|
270
284
|
klass.class_eval %{
|
data/lib/og/store/psql.rb
CHANGED
@@ -43,6 +43,27 @@ module PsqlUtils
|
|
43
43
|
return nil unless str
|
44
44
|
return PGconn.escape(str)
|
45
45
|
end
|
46
|
+
|
47
|
+
# TODO, mneumann:
|
48
|
+
#
|
49
|
+
# Blobs are actually a lot faster (and uses up less storage) for large data I
|
50
|
+
# think, as they need not to be encoded and decoded. I'd like to have both ;-)
|
51
|
+
# BYTEA is easier to handle than BLOBs, but if you implement BLOBs in a way
|
52
|
+
# that they are transparent to the user (as I did in Ruby/DBI), I'd prefer that
|
53
|
+
# way.
|
54
|
+
|
55
|
+
def blob(val)
|
56
|
+
val.gsub(/[\000-\037\047\134\177-\377]/) do |b|
|
57
|
+
"\\#{ b[0].to_s(8).rjust(3, '0') }"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def parse_blob(val)
|
62
|
+
val.gsub(/\\(\\|'|[0-3][0-7][0-7])/) do |s|
|
63
|
+
if s.size == 2 then s[1,1] else s[1,3].oct.chr end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
46
67
|
end
|
47
68
|
|
48
69
|
# A Store that persists objects into a PostgreSQL database.
|
@@ -122,6 +143,14 @@ class PsqlStore < SqlStore
|
|
122
143
|
handle_sql_exception(ex, sql)
|
123
144
|
end
|
124
145
|
|
146
|
+
def sql_update(sql)
|
147
|
+
Logger.debug sql if $DBG
|
148
|
+
res = @conn.exec(sql)
|
149
|
+
changed = res.cmdtuples
|
150
|
+
res.clear
|
151
|
+
changed
|
152
|
+
end
|
153
|
+
|
125
154
|
private
|
126
155
|
|
127
156
|
def create_table(klass)
|
@@ -214,6 +243,8 @@ private
|
|
214
243
|
return "#{self.class}.parse_date(res.getvalue(row, #{col} + offset))"
|
215
244
|
elsif p.klass.ancestors.include?(TrueClass)
|
216
245
|
return %|('t' == res.getvalue(row, #{col} + offset))|
|
246
|
+
elsif p.klass.ancestors.include?(Og::Blob)
|
247
|
+
return "#{self.class}.parse_blob(res.getvalue(row, #{col} + offset))"
|
217
248
|
else
|
218
249
|
return "YAML.load(res.getvalue(row, #{col} + offset))"
|
219
250
|
end
|
@@ -226,6 +257,11 @@ private
|
|
226
257
|
def eval_og_insert(klass)
|
227
258
|
props = klass.properties
|
228
259
|
values = props.collect { |p| write_prop(p) }.join(',')
|
260
|
+
|
261
|
+
if klass.metadata.superclass or klass.metadata.subclasses
|
262
|
+
props << Property.new(:ogtype, String)
|
263
|
+
values << ", '#{klass}'"
|
264
|
+
end
|
229
265
|
|
230
266
|
sql = "INSERT INTO #{klass::OGTABLE} (#{props.collect {|p| p.symbol.to_s}.join(',')}) VALUES (#{values})"
|
231
267
|
|
@@ -244,3 +280,6 @@ private
|
|
244
280
|
end
|
245
281
|
|
246
282
|
end
|
283
|
+
|
284
|
+
# * George Moschovitis <gm@navel.gr>
|
285
|
+
# * Michael Neumann <mneumann@ntecs.de>
|
data/lib/og/store/sql.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
require 'yaml'
|
2
2
|
|
3
|
+
require 'facet/object/constant'
|
4
|
+
|
3
5
|
module Og
|
4
6
|
|
5
7
|
module SqlUtils
|
@@ -31,6 +33,14 @@ module SqlUtils
|
|
31
33
|
return "#{date.year}-#{date.month}-#{date.mday}"
|
32
34
|
end
|
33
35
|
|
36
|
+
#--
|
37
|
+
# TODO: implement me!
|
38
|
+
#++
|
39
|
+
|
40
|
+
def blob(val)
|
41
|
+
val
|
42
|
+
end
|
43
|
+
|
34
44
|
# Parse an integer.
|
35
45
|
|
36
46
|
def parse_int(int)
|
@@ -65,6 +75,16 @@ module SqlUtils
|
|
65
75
|
return Date.strptime(str)
|
66
76
|
end
|
67
77
|
|
78
|
+
#--
|
79
|
+
# TODO: implement me!!
|
80
|
+
#++
|
81
|
+
|
82
|
+
def parse_blob(val)
|
83
|
+
val
|
84
|
+
end
|
85
|
+
|
86
|
+
# Escape the various Ruby types.
|
87
|
+
|
68
88
|
def quote(val)
|
69
89
|
case val
|
70
90
|
when Fixnum, Integer, Float
|
@@ -141,17 +161,49 @@ class SqlStore < Store
|
|
141
161
|
# Enchants a class.
|
142
162
|
|
143
163
|
def enchant(klass, manager)
|
144
|
-
|
164
|
+
|
165
|
+
# setup the table where this class is mapped.
|
166
|
+
|
167
|
+
if sclass = klass.metadata.superclass
|
168
|
+
klass.const_set 'OGTABLE', table(sclass.first)
|
169
|
+
else
|
170
|
+
klass.const_set 'OGTABLE', table(klass)
|
171
|
+
end
|
172
|
+
|
145
173
|
klass.module_eval 'def self.table; OGTABLE; end'
|
174
|
+
|
175
|
+
# precompile a class specific allocate method. If this
|
176
|
+
# is an STI parent classes it reads the class from the
|
177
|
+
# resultset.
|
178
|
+
|
179
|
+
if klass.metadata.subclasses
|
180
|
+
klass.module_eval %{
|
181
|
+
def self.og_allocate(res)
|
182
|
+
Object.constant(res[0]).allocate
|
183
|
+
end
|
184
|
+
}
|
185
|
+
else
|
186
|
+
klass.module_eval %{
|
187
|
+
def self.og_allocate(res)
|
188
|
+
self.allocate
|
189
|
+
end
|
190
|
+
}
|
191
|
+
end
|
146
192
|
|
147
193
|
super
|
148
|
-
|
149
|
-
create_table(klass) if Og.create_schema
|
150
194
|
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
195
|
+
unless klass.polymorphic_parent?
|
196
|
+
# create the table if needed.
|
197
|
+
|
198
|
+
create_table(klass) if Og.create_schema
|
199
|
+
|
200
|
+
# precompile class specific lifecycle methods.
|
201
|
+
|
202
|
+
eval_og_insert(klass)
|
203
|
+
eval_og_update(klass)
|
204
|
+
eval_og_read(klass)
|
205
|
+
eval_og_delete(klass)
|
206
|
+
end
|
155
207
|
end
|
156
208
|
|
157
209
|
# :section: Lifecycle methods.
|
@@ -162,6 +214,7 @@ class SqlStore < Store
|
|
162
214
|
res = query "SELECT * FROM #{klass::OGTABLE} WHERE #{klass.pk_symbol}=#{pk}"
|
163
215
|
read_one(res, klass)
|
164
216
|
end
|
217
|
+
alias_method :exist?, :load
|
165
218
|
|
166
219
|
# Reloads an object from the store.
|
167
220
|
|
@@ -177,8 +230,8 @@ class SqlStore < Store
|
|
177
230
|
# selected properties. Pass the required properties as symbols
|
178
231
|
# or strings.
|
179
232
|
|
180
|
-
def update(obj,
|
181
|
-
if properties
|
233
|
+
def update(obj, options = nil)
|
234
|
+
if options and properties = options[:only]
|
182
235
|
if properties.is_a?(Array)
|
183
236
|
set = []
|
184
237
|
for p in properties
|
@@ -188,9 +241,11 @@ class SqlStore < Store
|
|
188
241
|
else
|
189
242
|
set = "#{properties}=#{quote(obj.send(properties))}"
|
190
243
|
end
|
191
|
-
|
244
|
+
sql = "UPDATE #{obj.class.table} SET #{set} WHERE #{obj.class.pk_symbol}=#{obj.pk}"
|
245
|
+
sql << " AND #{options[:condition]}" if options[:condition]
|
246
|
+
sql_update(sql)
|
192
247
|
else
|
193
|
-
obj.og_update(self)
|
248
|
+
obj.og_update(self, options)
|
194
249
|
end
|
195
250
|
end
|
196
251
|
|
@@ -202,14 +257,12 @@ class SqlStore < Store
|
|
202
257
|
|
203
258
|
if target.is_a?(Class)
|
204
259
|
sql = "UPDATE #{target.table} SET #{set} "
|
205
|
-
if options
|
206
|
-
|
207
|
-
sql << " WHERE #{condition}"
|
208
|
-
end
|
209
|
-
end
|
210
|
-
exec sql
|
260
|
+
sql << " WHERE #{options[:condition]}" if options and options[:condition]
|
261
|
+
sql_update(sql)
|
211
262
|
else
|
212
|
-
|
263
|
+
sql = "UPDATE #{target.class.table} SET #{set} WHERE #{target.class.pk_symbol}=#{target.pk}"
|
264
|
+
sql << " AND #{options[:condition]}" if options and options[:condition]
|
265
|
+
sql_update(sql)
|
213
266
|
end
|
214
267
|
end
|
215
268
|
alias_method :pupdate, :update_properties
|
@@ -237,6 +290,21 @@ class SqlStore < Store
|
|
237
290
|
read_one(query(sql), klass, options[:include])
|
238
291
|
end
|
239
292
|
|
293
|
+
# Perform a custom sql query and deserialize the
|
294
|
+
# results.
|
295
|
+
|
296
|
+
def select(sql, klass)
|
297
|
+
sql = "SELECT * FROM #{klass.table} " + sql unless sql =~ /SELECT/
|
298
|
+
read_all(query(sql), klass)
|
299
|
+
end
|
300
|
+
|
301
|
+
# Specialized one result version of select.
|
302
|
+
|
303
|
+
def select_one(sql, klass)
|
304
|
+
sql = "SELECT * FROM #{klass.table} " + sql unless sql =~ /SELECT/
|
305
|
+
read_one(query(sql), klass)
|
306
|
+
end
|
307
|
+
|
240
308
|
def count(options)
|
241
309
|
if options.is_a?(String)
|
242
310
|
sql = options
|
@@ -295,6 +363,15 @@ class SqlStore < Store
|
|
295
363
|
exec('ROLLBACK') if @transaction_nesting < 1
|
296
364
|
end
|
297
365
|
|
366
|
+
# :section: Low level methods.
|
367
|
+
|
368
|
+
# Encapsulates a low level update method.
|
369
|
+
|
370
|
+
def sql_update(sql)
|
371
|
+
exec(sql)
|
372
|
+
# return affected rows.
|
373
|
+
end
|
374
|
+
|
298
375
|
private
|
299
376
|
|
300
377
|
def create_table(klass)
|
@@ -313,8 +390,22 @@ private
|
|
313
390
|
|
314
391
|
def fields_for_class(klass)
|
315
392
|
fields = []
|
393
|
+
properties = klass.properties
|
394
|
+
|
395
|
+
if subclasses = klass.metadata.subclasses
|
396
|
+
# This class as a superclass in a single table inheritance
|
397
|
+
# chain. So inject a special class ogtype field that
|
398
|
+
# holds the class name.
|
399
|
+
fields << "ogtype VARCHAR(30)"
|
400
|
+
|
401
|
+
for subclass in subclasses
|
402
|
+
properties.concat(subclass.properties)
|
403
|
+
end
|
404
|
+
|
405
|
+
properties.uniq!
|
406
|
+
end
|
316
407
|
|
317
|
-
|
408
|
+
properties.each do |p|
|
318
409
|
klass.index(p.symbol) if p.meta[:index]
|
319
410
|
|
320
411
|
field = p.symbol.to_s
|
@@ -368,6 +459,8 @@ private
|
|
368
459
|
return %|#\{@#{p.symbol} ? "'#\{#{self.class}.date(@#{p.symbol})\}'" : 'NULL'\}|
|
369
460
|
elsif p.klass.ancestors.include?(TrueClass)
|
370
461
|
return "#\{@#{p.symbol} ? \"'t'\" : 'NULL' \}"
|
462
|
+
elsif p.klass.ancestors.include?(Og::Blob)
|
463
|
+
return %|#\{@#{p.symbol} ? "'#\{#{self.class}.escape(#{self.class}.blob(@#{p.symbol}))\}'" : 'NULL'\}|
|
371
464
|
else
|
372
465
|
# gmosx: keep the '' for nil symbols.
|
373
466
|
return %|#\{@#{p.symbol} ? "'#\{#{self.class}.escape(@#{p.symbol}.to_yaml)\}'" : "''"\}|
|
@@ -390,6 +483,8 @@ private
|
|
390
483
|
return "#{self.class}.parse_date(res[#{col} + offset])"
|
391
484
|
elsif p.klass.ancestors.include?(TrueClass)
|
392
485
|
return "('0' != res[#{col} + offset])"
|
486
|
+
elsif p.klass.ancestors.include?(Og::Blob)
|
487
|
+
return "#{self.class}.parse_blob(res[#{col} + offset])"
|
393
488
|
else
|
394
489
|
return "YAML::load(res[#{col} + offset])"
|
395
490
|
end
|
@@ -397,12 +492,17 @@ private
|
|
397
492
|
|
398
493
|
# :section: Lifecycle method compilers.
|
399
494
|
|
400
|
-
# Compile the
|
495
|
+
# Compile the og_insert method for the class.
|
401
496
|
|
402
497
|
def eval_og_insert(klass)
|
403
498
|
pk = klass.pk_symbol
|
404
499
|
props = klass.properties
|
405
500
|
values = props.collect { |p| write_prop(p) }.join(',')
|
501
|
+
|
502
|
+
if klass.metadata.superclass or klass.metadata.subclasses
|
503
|
+
props << Property.new(:ogtype, String)
|
504
|
+
values << ", '#{klass}'"
|
505
|
+
end
|
406
506
|
|
407
507
|
sql = "INSERT INTO #{klass.table} (#{props.collect {|p| p.symbol.to_s}.join(',')}) VALUES (#{values})"
|
408
508
|
|
@@ -428,10 +528,13 @@ private
|
|
428
528
|
sql = "UPDATE #{klass::OGTABLE} SET #{updates.join(', ')} WHERE #{pk}=#\{@#{pk}\}"
|
429
529
|
|
430
530
|
klass.module_eval %{
|
431
|
-
def og_update(store)
|
531
|
+
def og_update(store, options = nil)
|
432
532
|
#{Aspects.gen_advice_code(:og_update, klass.advices, :pre) if klass.respond_to?(:advices)}
|
433
|
-
|
533
|
+
sql = "#{sql}"
|
534
|
+
sql << " AND \#{options[:condition]}" if options and options[:condition]
|
535
|
+
changed = store.sql_update(sql)
|
434
536
|
#{Aspects.gen_advice_code(:og_update, klass.advices, :post) if klass.respond_to?(:advices)}
|
537
|
+
return changed
|
435
538
|
end
|
436
539
|
}
|
437
540
|
end
|
@@ -474,8 +577,8 @@ private
|
|
474
577
|
pk ||= @#{klass.pk_symbol}
|
475
578
|
transaction do |tx|
|
476
579
|
tx.exec "DELETE FROM #{klass.table} WHERE #{klass.pk_symbol}=\#{pk}"
|
477
|
-
if cascade and #{klass}.
|
478
|
-
#{klass}.
|
580
|
+
if cascade and #{klass}.metadata[:descendants]
|
581
|
+
#{klass}.metadata[:descendants].each do |dclass, foreign_key|
|
479
582
|
tx.exec "DELETE FROM \#{dclass::OGTABLE} WHERE \#{foreign_key}=\#{pk}"
|
480
583
|
end
|
481
584
|
end
|
@@ -497,6 +600,11 @@ private
|
|
497
600
|
end
|
498
601
|
|
499
602
|
def resolve_options(klass, options)
|
603
|
+
if sql = options[:sql]
|
604
|
+
sql = "SELECT * FROM #{klass.table} " + sql unless sql =~ /SELECT/
|
605
|
+
return sql
|
606
|
+
end
|
607
|
+
|
500
608
|
tables = [klass::OGTABLE]
|
501
609
|
|
502
610
|
if included = options[:include]
|
@@ -514,22 +622,18 @@ private
|
|
514
622
|
|
515
623
|
fields = tables.collect { |t| "#{t}.*" }.join(',')
|
516
624
|
|
517
|
-
|
518
|
-
options[:condition] += " AND #{join_conditions.join(' AND ')}"
|
519
|
-
else
|
520
|
-
options[:condition] = join_conditions.join(' AND ')
|
521
|
-
end
|
625
|
+
update_condition options, join_conditions.join(' AND ')
|
522
626
|
else
|
523
627
|
fields = '*'
|
524
628
|
end
|
525
629
|
|
526
630
|
if join_table = options[:join_table]
|
527
631
|
tables << join_table
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
632
|
+
update_condition options, options[:join_condition]
|
633
|
+
end
|
634
|
+
|
635
|
+
if ogtype = options[:type]
|
636
|
+
update_condition options, "ogtype='#{ogtype}'"
|
533
637
|
end
|
534
638
|
|
535
639
|
sql = "SELECT #{fields} FROM #{tables.join(',')}"
|
@@ -561,12 +665,12 @@ private
|
|
561
665
|
|
562
666
|
# Deserialize the join relations.
|
563
667
|
|
564
|
-
def read_join_relations(obj,
|
668
|
+
def read_join_relations(obj, res_row, row, join_relations)
|
565
669
|
offset = obj.class.properties.size
|
566
670
|
|
567
671
|
for rel in join_relations
|
568
|
-
rel_obj = rel[:target_class].
|
569
|
-
rel_obj.og_read(
|
672
|
+
rel_obj = rel[:target_class].og_allocate(res_row)
|
673
|
+
rel_obj.og_read(res_row, row, offset)
|
570
674
|
offset += rel_obj.class.properties.size
|
571
675
|
obj.instance_variable_set("@#{rel[:name]}", rel_obj)
|
572
676
|
end
|
@@ -583,20 +687,21 @@ private
|
|
583
687
|
end
|
584
688
|
end
|
585
689
|
|
586
|
-
|
690
|
+
res_row = res.next
|
587
691
|
|
588
|
-
obj = klass.
|
589
|
-
obj.og_read(
|
590
|
-
read_join_relations(obj,
|
591
|
-
|
592
|
-
res.close
|
692
|
+
obj = klass.og_allocate(res_row)
|
693
|
+
obj.og_read(res_row)
|
694
|
+
read_join_relations(obj, res_row, 0, join_relations) if join_relations
|
593
695
|
|
594
696
|
return obj
|
697
|
+
|
698
|
+
ensure
|
699
|
+
res.close
|
595
700
|
end
|
596
701
|
|
597
702
|
# Deserialize all objects from the ResultSet.
|
598
703
|
|
599
|
-
def read_all(res, klass, join_relations)
|
704
|
+
def read_all(res, klass, join_relations = nil)
|
600
705
|
return [] if res.blank?
|
601
706
|
|
602
707
|
if join_relations
|
@@ -608,15 +713,26 @@ private
|
|
608
713
|
objects = []
|
609
714
|
|
610
715
|
res.each_row do |res_row, row|
|
611
|
-
obj = klass.
|
716
|
+
obj = klass.og_allocate(res_row)
|
612
717
|
obj.og_read(res_row, row)
|
613
718
|
read_join_relations(obj, res_row, row, join_relations) if join_relations
|
614
719
|
objects << obj
|
615
720
|
end
|
616
721
|
|
617
|
-
res.close
|
618
|
-
|
619
722
|
return objects
|
723
|
+
|
724
|
+
ensure
|
725
|
+
res.close
|
726
|
+
end
|
727
|
+
|
728
|
+
# Helper method that updates the condition string.
|
729
|
+
|
730
|
+
def update_condition(options, cond, joiner = 'AND')
|
731
|
+
if options[:condition]
|
732
|
+
options[:condition] += " #{joiner} #{cond}"
|
733
|
+
else
|
734
|
+
options[:condition] = cond
|
735
|
+
end
|
620
736
|
end
|
621
737
|
|
622
738
|
end
|
@@ -624,3 +740,5 @@ end
|
|
624
740
|
end
|
625
741
|
|
626
742
|
# * George Moschovitis <gm@navel.gr>
|
743
|
+
# * Michael Neumann <mneumann@ntecs.de>
|
744
|
+
# * Ghislain Mary
|