og 0.18.1 → 0.19.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 +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
|