og 0.18.1 → 0.19.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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