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.
@@ -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>
@@ -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
- @options[:target_pk], @options[:target_pkclass] = target_class.primary_key
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.resolve_target
100
- relation.resolve_options
101
- relation.enchant
150
+ relation.enchant unless relation.target_class == Object
102
151
  end
103
152
  end
104
153
  end
@@ -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
@@ -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, properties = nil)
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
@@ -115,7 +115,7 @@ class MysqlStore < SqlStore
115
115
  end
116
116
 
117
117
  def query(sql)
118
- # Logger.debug sql if $DBG
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
- # Logger.debug sql if $DBG
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
- sql << ");"
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 %{
@@ -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>
@@ -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
- klass.const_set 'OGTABLE', table(klass)
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
- eval_og_insert(klass)
152
- eval_og_update(klass)
153
- eval_og_read(klass)
154
- eval_og_delete(klass)
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, properties = nil)
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
- exec "UPDATE #{obj.class.table} SET #{set} WHERE #{obj.class.pk_symbol}=#{obj.pk}"
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
- if condition = options[:condition] || options[:where]
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
- exec "UPDATE #{target.class.table} SET #{set} WHERE #{target.class.pk_symbol}=#{target.pk}"
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
- klass.properties.each do |p|
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 og_update method for the class.
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
- store.exec "#{sql}"
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}.__meta[:descendants]
478
- #{klass}.__meta[:descendants].each do |dclass, foreign_key|
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
- if options[:condition]
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
- if options[:condition]
529
- options[:condition] += " AND #{options[:join_condition]}"
530
- else
531
- options[:condition] = options[:join_condition]
532
- end
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, res, row, join_relations)
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].allocate
569
- rel_obj.og_read(res, row, offset)
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
- row = res.next
690
+ res_row = res.next
587
691
 
588
- obj = klass.allocate
589
- obj.og_read(row)
590
- read_join_relations(obj, row, 0, join_relations) if join_relations
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.allocate
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