lafcadio 0.8.1 → 0.8.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -280,7 +280,7 @@ module Lafcadio
280
280
  value = @obj.send(field.name)
281
281
  unless field.db_will_automatically_write
282
282
  nameValues << field.name_for_sql
283
- nameValues <<(field.value_for_sql(value))
283
+ nameValues << field.value_for_sql(value)
284
284
  end
285
285
  if field.bind_write?
286
286
  @bind_values << value
@@ -418,6 +418,7 @@ module Lafcadio
418
418
  # myDomainObject.commit
419
419
  def commit(db_object)
420
420
  @cache.commit( db_object )
421
+ db_object.reset_original_values_hash
421
422
  db_object
422
423
  end
423
424
 
@@ -582,11 +583,21 @@ module Lafcadio
582
583
  query.implies?( other_query )
583
584
  }
584
585
  if pk_ids
585
- @collections_by_query[query] = ( pk_ids.collect { |pk_id|
586
+ collection = ( pk_ids.collect { |pk_id|
586
587
  get( query.domain_class, pk_id )
587
588
  } ).select { |dobj| query.object_meets( dobj ) }.collect { |dobj|
588
589
  dobj.pk_id
589
590
  }
591
+ collection = collection[query.limit] if query.limit
592
+ if ( order_by = query.order_by )
593
+ collection = collection.sort_by { |pk_id|
594
+ get( query.domain_class, pk_id ).send( order_by )
595
+ }
596
+ collection.reverse! if query.order_by_order == Query::DESC
597
+ else
598
+ collection = collection.sort
599
+ end
600
+ @collections_by_query[query] = collection
590
601
  elsif @collections_by_query.values
591
602
  newObjects = @dbBridge.get_collection_by_query(query)
592
603
  newObjects.each { |dbObj| save dbObj }
@@ -0,0 +1,778 @@
1
+ require 'dbi'
2
+ require 'lafcadio/depend'
3
+ require 'lafcadio/domain'
4
+ require 'lafcadio/query'
5
+ require 'lafcadio/util'
6
+
7
+ module Lafcadio
8
+ class Committer #:nodoc:
9
+ INSERT = 1
10
+ UPDATE = 2
11
+ DELETE = 3
12
+
13
+ attr_reader :commit_type, :db_object
14
+
15
+ def initialize(db_object, dbBridge, cache)
16
+ @db_object = db_object
17
+ @dbBridge = dbBridge
18
+ @cache = cache
19
+ @objectStore = ObjectStore.get_object_store
20
+ @commit_type = nil
21
+ end
22
+
23
+ def execute
24
+ @db_object.verify if LafcadioConfig.new()['checkFields'] == 'onCommit'
25
+ set_commit_type
26
+ @db_object.last_commit = get_last_commit
27
+ @db_object.pre_commit_trigger
28
+ update_dependent_domain_objects if @db_object.delete
29
+ @dbBridge.commit @db_object
30
+ unless @db_object.pk_id
31
+ @db_object.pk_id = @dbBridge.last_pk_id_inserted
32
+ end
33
+ @cache.update_after_commit self
34
+ @db_object.post_commit_trigger
35
+ end
36
+
37
+ def get_last_commit
38
+ if @db_object.delete
39
+ DomainObject::COMMIT_DELETE
40
+ elsif @db_object.pk_id
41
+ DomainObject::COMMIT_EDIT
42
+ else
43
+ DomainObject::COMMIT_ADD
44
+ end
45
+ end
46
+
47
+ def set_commit_type
48
+ if @db_object.delete
49
+ @commit_type = DELETE
50
+ elsif @db_object.pk_id
51
+ @commit_type = UPDATE
52
+ else
53
+ @commit_type = INSERT
54
+ end
55
+ end
56
+
57
+ def update_dependent_domain_objects
58
+ dependent_classes = @db_object.domain_class.dependent_classes
59
+ dependent_classes.keys.each { |aClass|
60
+ field = dependent_classes[aClass]
61
+ collection = @objectStore.get_filtered( aClass.name, @db_object,
62
+ field.name )
63
+ collection.each { |dependentObject|
64
+ if field.delete_cascade
65
+ dependentObject.delete = true
66
+ else
67
+ dependentObject.send( field.name + '=', nil )
68
+ end
69
+ @objectStore.commit(dependentObject)
70
+ }
71
+ }
72
+ end
73
+ end
74
+
75
+ class CouldntMatchDomainClassError < RuntimeError #:nodoc:
76
+ end
77
+
78
+ class DbBridge #:nodoc:
79
+ @@dbh = nil
80
+ @@last_pk_id_inserted = nil
81
+
82
+ def self._load(aString)
83
+ aString =~ /dbh:/
84
+ dbString = $'
85
+ begin
86
+ dbh = Marshal.load(dbString)
87
+ rescue TypeError
88
+ dbh = nil
89
+ end
90
+ new dbh
91
+ end
92
+
93
+ def initialize
94
+ @db_conn = DbConnection.get_db_connection
95
+ ObjectSpace.define_finalizer( self, proc { |id|
96
+ DbConnection.get_db_connection.disconnect
97
+ } )
98
+ end
99
+
100
+ def _dump(aDepth)
101
+ dbDump = @dbh.respond_to?( '_dump' ) ? @dbh._dump : @dbh.class.to_s
102
+ "dbh:#{dbDump}"
103
+ end
104
+
105
+ def commit(db_object)
106
+ sqlMaker = DomainObjectSqlMaker.new(db_object)
107
+ sqlMaker.sql_statements.each { |sql, binds| execute_commit( sql, binds ) }
108
+ if sqlMaker.sql_statements[0].first =~ /insert/
109
+ sql = 'select last_insert_id()'
110
+ result = execute_select( sql )
111
+ @@last_pk_id_inserted = result[0]['last_insert_id()'].to_i
112
+ end
113
+ end
114
+
115
+ def execute_commit( sql, binds ); @db_conn.do( sql, *binds ); end
116
+
117
+ def execute_select(sql)
118
+ maybe_log sql
119
+ begin
120
+ @db_conn.select_all( sql )
121
+ rescue DBI::DatabaseError => e
122
+ raise $!.to_s + ": #{ e.errstr }"
123
+ end
124
+ end
125
+
126
+ def get_collection_by_query(query)
127
+ domain_class = query.domain_class
128
+ execute_select( query.to_sql ).collect { |row_hash|
129
+ domain_class.new( SqlValueConverter.new( domain_class, row_hash ) )
130
+ }
131
+ end
132
+
133
+ def group_query( query )
134
+ execute_select( query.to_sql )[0].collect { |val|
135
+ a_field = query.domain_class.get_field( query.field_name )
136
+ a_field.value_from_sql( val )
137
+ }
138
+ end
139
+
140
+ def last_pk_id_inserted; @@last_pk_id_inserted; end
141
+
142
+ def maybe_log(sql)
143
+ config = LafcadioConfig.new
144
+ if config['logSql'] == 'y'
145
+ sqllog = Log4r::Logger['sql'] || Log4r::Logger.new( 'sql' )
146
+ filename = File.join( config['logdir'], config['sqlLogFile'] || 'sql' )
147
+ outputter = Log4r::FileOutputter.new( 'outputter',
148
+ { :filename => filename } )
149
+ sqllog.outputters = outputter
150
+ sqllog.info sql
151
+ end
152
+ end
153
+ end
154
+
155
+ class DbConnection < ContextualService::Service
156
+ @@connectionClass = DBI
157
+ @@db_name = nil
158
+ @@dbh = nil
159
+
160
+ def self.flush
161
+ DbConnection.set_db_connection( nil )
162
+ @@dbh = nil
163
+ end
164
+
165
+ def self.set_connection_class( aClass ); @@connectionClass = aClass; end
166
+
167
+ def self.set_db_name( db_name ); @@db_name = db_name; end
168
+
169
+ def self.set_dbh( dbh ); @@dbh = dbh; end
170
+
171
+ def initialize
172
+ @@dbh = load_new_dbh if @@dbh.nil?
173
+ @dbh = @@dbh
174
+ end
175
+
176
+ def disconnect; @dbh.disconnect if @dbh; end
177
+
178
+ def load_new_dbh
179
+ config = LafcadioConfig.new
180
+ dbName = @@db_name || config['dbname']
181
+ dbAndHost = nil
182
+ if dbName && config['dbhost']
183
+ dbAndHost = "dbi:Mysql:#{ dbName }:#{ config['dbhost'] }"
184
+ else
185
+ dbAndHost = "dbi:#{config['dbconn']}"
186
+ end
187
+ @@dbh = @@connectionClass.connect( dbAndHost, config['dbuser'],
188
+ config['dbpassword'] )
189
+ end
190
+
191
+ def method_missing( symbol, *args )
192
+ @dbh.send( symbol, *args )
193
+ end
194
+ end
195
+
196
+ class DomainObjectInitError < RuntimeError #:nodoc:
197
+ attr_reader :messages
198
+
199
+ def initialize(messages)
200
+ @messages = messages
201
+ end
202
+ end
203
+
204
+ class DomainObjectNotFoundError < RuntimeError #:nodoc:
205
+ end
206
+
207
+ # The DomainObjectProxy is used when retrieving domain objects that are
208
+ # linked to other domain objects with DomainObjectFields. In terms of +domain_class+
209
+ # and
210
+ # +pk_id+, a DomainObjectProxy instance looks to the outside world like the
211
+ # domain object it's supposed to represent. It only retrieves its domain
212
+ # object from the database when member data is requested.
213
+ #
214
+ # In normal usage you will probably never manipulate a DomainObjectProxy
215
+ # directly, but you may discover it by accident by calling
216
+ # DomainObjectProxy#class (or DomainObject#class) instead of
217
+ # DomainObjectProxy#domain_class (or DomainObjectProxy#domain_class).
218
+ class DomainObjectProxy
219
+ include DomainComparable
220
+
221
+ attr_accessor :domain_class, :pk_id
222
+
223
+ def initialize(domain_classOrDbObject, pk_id = nil)
224
+ if pk_id
225
+ @domain_class = domain_classOrDbObject
226
+ @pk_id = pk_id
227
+ elsif domain_classOrDbObject.class < DomainObject
228
+ @db_object = domain_classOrDbObject
229
+ @d_obj_retrieve_time = Time.now
230
+ @domain_class = @db_object.class
231
+ @pk_id = @db_object.pk_id
232
+ else
233
+ raise ArgumentError
234
+ end
235
+ @db_object = nil
236
+ end
237
+
238
+ def get_db_object
239
+ object_store = ObjectStore.get_object_store
240
+ if @db_object.nil? || needs_refresh?
241
+ @db_object = object_store.get( @domain_class, @pk_id )
242
+ @d_obj_retrieve_time = Time.now
243
+ end
244
+ @db_object
245
+ end
246
+
247
+ def hash
248
+ get_db_object.hash
249
+ end
250
+
251
+ def method_missing(methodId, *args)
252
+ get_db_object.send(methodId.id2name, *args)
253
+ end
254
+
255
+ def needs_refresh?
256
+ object_store = ObjectStore.get_object_store
257
+ last_commit_time = object_store.last_commit_time( @domain_class, @pk_id )
258
+ !last_commit_time.nil? && last_commit_time > @d_obj_retrieve_time
259
+ end
260
+
261
+ def to_s
262
+ get_db_object.to_s
263
+ end
264
+ end
265
+
266
+ class DomainObjectSqlMaker #:nodoc:
267
+ attr_reader :bind_values
268
+
269
+ def initialize(obj); @obj = obj; end
270
+
271
+ def delete_sql( domain_class )
272
+ "delete from #{ domain_class.table_name} " +
273
+ "where #{ domain_class.sql_primary_key_name }=#{ @obj.pk_id }"
274
+ end
275
+
276
+ def get_name_value_pairs( domain_class )
277
+ nameValues = []
278
+ domain_class.class_fields.each { |field|
279
+ unless field.instance_of?( PrimaryKeyField )
280
+ value = @obj.send(field.name)
281
+ unless field.db_will_automatically_write
282
+ nameValues << field.name_for_sql
283
+ nameValues << field.value_for_sql(value)
284
+ end
285
+ if field.bind_write?
286
+ @bind_values << value
287
+ end
288
+ end
289
+ }
290
+ QueueHash.new( *nameValues )
291
+ end
292
+
293
+ def insert_sql( domain_class )
294
+ fields = domain_class.class_fields
295
+ nameValuePairs = get_name_value_pairs( domain_class )
296
+ if domain_class.is_based_on?
297
+ nameValuePairs[domain_class.sql_primary_key_name] = 'LAST_INSERT_ID()'
298
+ end
299
+ fieldNameStr = nameValuePairs.keys.join ", "
300
+ fieldValueStr = nameValuePairs.values.join ", "
301
+ "insert into #{ domain_class.table_name}(#{fieldNameStr}) " +
302
+ "values(#{fieldValueStr})"
303
+ end
304
+
305
+ def sql_statements
306
+ statements = []
307
+ if @obj.error_messages.size > 0
308
+ raise DomainObjectInitError, @obj.error_messages, caller
309
+ end
310
+ @obj.class.self_and_concrete_superclasses.each { |domain_class|
311
+ statements << statement_bind_value_pair( domain_class )
312
+ }
313
+ statements.reverse
314
+ end
315
+
316
+ def statement_bind_value_pair( domain_class )
317
+ @bind_values = []
318
+ if @obj.pk_id == nil
319
+ statement = insert_sql( domain_class )
320
+ else
321
+ if @obj.delete
322
+ statement = delete_sql( domain_class )
323
+ else
324
+ statement = update_sql( domain_class)
325
+ end
326
+ end
327
+ [statement, @bind_values]
328
+ end
329
+
330
+ def update_sql( domain_class )
331
+ nameValueStrings = []
332
+ nameValuePairs = get_name_value_pairs( domain_class )
333
+ nameValuePairs.each { |key, value|
334
+ nameValueStrings << "#{key}=#{ value }"
335
+ }
336
+ allNameValues = nameValueStrings.join ', '
337
+ "update #{ domain_class.table_name} set #{allNameValues} " +
338
+ "where #{ domain_class.sql_primary_key_name}=#{@obj.pk_id}"
339
+ end
340
+ end
341
+
342
+ class FieldMatchError < StandardError; end
343
+
344
+ # The ObjectStore represents the database in a Lafcadio application.
345
+ #
346
+ # = Configuring the ObjectStore
347
+ # The ObjectStore depends on a few values being set correctly in the
348
+ # LafcadioConfig file:
349
+ # [dbuser] The database username.
350
+ # [dbpassword] The database password.
351
+ # [dbname] The database name.
352
+ # [dbhost] The database host.
353
+ #
354
+ # = Instantiating ObjectStore
355
+ # The ObjectStore is a ContextualService, meaning you can't get an instance by
356
+ # calling ObjectStore.new. Instead, you should call
357
+ # ObjectStore.get_object_store. (Using a ContextualService makes it easier to
358
+ # make out the ObjectStore for unit tests: See ContextualService for more.)
359
+ #
360
+ # = Dynamic method calls
361
+ # ObjectStore uses reflection to provide a lot of convenience methods for
362
+ # querying domain objects in a number of ways.
363
+ # [ObjectStore#get< domain class > (pk_id)]
364
+ # Retrieves one domain object by pk_id. For example,
365
+ # ObjectStore#getUser( 100 )
366
+ # will return User 100.
367
+ # [ObjectStore#get< domain class >s (searchTerm, fieldName = nil)]
368
+ # Returns a collection of all instances of that domain class matching that
369
+ # search term. For example,
370
+ # ObjectStore#getProducts( aProductCategory )
371
+ # queries MySQL for all products that belong to that product category. You
372
+ # can omit +fieldName+ if +searchTerm+ is a non-nil domain object, and the
373
+ # field connecting the first domain class to the second is named after the
374
+ # domain class. (For example, the above line assumes that Product has a
375
+ # field named "productCategory".) Otherwise, it's best to include
376
+ # +fieldName+:
377
+ # ObjectStore#getUsers( "Jones", "lastName" )
378
+ #
379
+ # = Querying
380
+ # ObjectStore can also be used to generate complex, ad-hoc queries which
381
+ # emulate much of the functionality you'd get from writing the SQL yourself.
382
+ # Furthermore, these queries can be run against in-memory data stores, which
383
+ # is particularly useful for tests.
384
+ # date = Date.new( 2003, 1, 1 )
385
+ # ObjectStore#getInvoices { |invoice|
386
+ # Query.And( invoice.date.gte( date ), invoice.rate.equals( 10 ),
387
+ # invoice.hours.equals( 10 ) )
388
+ # }
389
+ # is the same as
390
+ # select * from invoices
391
+ # where (date >= '2003-01-01' and rate = 10 and hours = 10)
392
+ # See lafcadio/query.rb for more.
393
+ #
394
+ # = SQL Logging
395
+ # Lafcadio uses log4r to log all of its SQL statements. The simplest way to
396
+ # turn on logging is to set the following values in the LafcadioConfig file:
397
+ # [logSql] Should be set to "y" to turn on logging.
398
+ # [logdir] The directory where log files should be written. Required if
399
+ # +logSql+ is "y"
400
+ # [sqlLogFile] The name of the file (not including its directory) where SQL
401
+ # should be logged. Default is "sql".
402
+ #
403
+ # = Triggers
404
+ # Domain classes can be set to fire triggers either before or after commits.
405
+ # Since these triggers are executed in Ruby, they're easy to test. See
406
+ # DomainObject#pre_commit_trigger and DomainObject#post_commit_trigger for more.
407
+ class ObjectStore < ContextualService::Service
408
+ def self.set_db_name(dbName) #:nodoc:
409
+ DbConnection.set_db_name dbName
410
+ end
411
+
412
+ def initialize( dbBridge = nil ) #:nodoc:
413
+ @dbBridge = dbBridge == nil ? DbBridge.new : dbBridge
414
+ @cache = ObjectStore::Cache.new( @dbBridge )
415
+ end
416
+
417
+ # Commits a domain object to the database. You can also simply call
418
+ # myDomainObject.commit
419
+ def commit(db_object)
420
+ @cache.commit( db_object )
421
+ db_object
422
+ end
423
+
424
+ # Flushes one domain object from its cache.
425
+ def flush(db_object)
426
+ @cache.flush db_object
427
+ end
428
+
429
+ # Returns the domain object corresponding to the domain class and pk_id.
430
+ def get( domain_class, pk_id )
431
+ query = Query.new domain_class, pk_id
432
+ @cache.get_by_query( query )[0] ||
433
+ ( raise( DomainObjectNotFoundError,
434
+ "Can't find #{domain_class} #{pk_id}", caller ) )
435
+ end
436
+
437
+ # Returns all domain objects for the given domain class.
438
+ def get_all(domain_class); @cache.get_by_query( Query.new( domain_class ) ); end
439
+
440
+ # Returns the DbBridge; this is useful in case you need to use raw SQL for a
441
+ # specific query.
442
+ def get_db_bridge; @dbBridge; end
443
+
444
+ def get_field_name( domain_object )
445
+ domain_object.domain_class.basename.camel_case_to_underscore
446
+ end
447
+
448
+ def get_filtered(domain_class_name, searchTerm, fieldName = nil) #:nodoc:
449
+ domain_class = DomainObject.get_domain_class_from_string(
450
+ domain_class_name
451
+ )
452
+ unless fieldName
453
+ fieldName = domain_class.get_link_field( searchTerm.domain_class ).name
454
+ end
455
+ get_subset( Query::Equals.new( fieldName, searchTerm, domain_class ) )
456
+ end
457
+
458
+ def get_map_match( domain_class, mapped ) #:nodoc:
459
+ Query::Equals.new( get_field_name( mapped ), mapped, domain_class )
460
+ end
461
+
462
+ def get_map_object( domain_class, map1, map2 ) #:nodoc:
463
+ unless map1 && map2
464
+ raise ArgumentError,
465
+ "ObjectStore#get_map_object needs two non-nil keys", caller
466
+ end
467
+ mapMatch1 = get_map_match domain_class, map1
468
+ mapMatch2 = get_map_match domain_class, map2
469
+ condition = Query::CompoundCondition.new mapMatch1, mapMatch2
470
+ get_subset(condition)[0]
471
+ end
472
+
473
+ def get_mapped(searchTerm, resultTypeName) #:nodoc:
474
+ resultType = DomainObject.get_domain_class_from_string resultTypeName
475
+ firstTypeName = searchTerm.class.basename
476
+ secondTypeName = resultType.basename
477
+ mapTypeName = firstTypeName + secondTypeName
478
+ get_filtered( mapTypeName, searchTerm ).collect { |mapObj|
479
+ mapObj.send( resultType.name.decapitalize )
480
+ }
481
+ end
482
+
483
+ # Retrieves the maximum value across all instances of one domain class.
484
+ # ObjectStore#get_max( Client )
485
+ # returns the highest +pk_id+ in the +clients+ table.
486
+ # ObjectStore#get_max( Invoice, "rate" )
487
+ # will return the highest rate for all invoices.
488
+ def get_max( domain_class, field_name = 'pk_id' )
489
+ @dbBridge.group_query( Query::Max.new( domain_class, field_name ) ).only
490
+ end
491
+
492
+ # Retrieves a collection of domain objects by +pk_id+.
493
+ # ObjectStore#get_objects( Clients, [ 1, 2, 3 ] )
494
+ def get_objects( domain_class, pk_ids )
495
+ if pk_ids.is_a?( Array ) && pk_ids.all? { |elt| elt.is_a?( Integer ) }
496
+ get_subset Query::In.new( 'pk_id', pk_ids, domain_class )
497
+ else
498
+ raise(
499
+ ArgumentError,
500
+ "ObjectStore#get_objects( domain_class, pk_ids ): pk_ids needs to " +
501
+ "be an array of integers",
502
+ caller
503
+ )
504
+ end
505
+ end
506
+
507
+ def get_subset(conditionOrQuery) #:nodoc:
508
+ if conditionOrQuery.class <= Query::Condition
509
+ condition = conditionOrQuery
510
+ query = Query.new condition.domain_class, condition
511
+ else
512
+ query = conditionOrQuery
513
+ end
514
+ @cache.get_by_query( query )
515
+ end
516
+
517
+ def last_commit_time( domain_class, pk_id ) #:nodoc:
518
+ @cache.last_commit_time( domain_class, pk_id )
519
+ end
520
+
521
+ def method_missing(methodId, *args) #:nodoc:
522
+ proc = block_given? ? ( proc { |obj| yield( obj ) } ) : nil
523
+ dispatch = MethodDispatch.new( methodId, proc, *args )
524
+ self.send( dispatch.symbol, *dispatch.args )
525
+ end
526
+
527
+ def mock? #:nodoc:
528
+ false
529
+ end
530
+
531
+ def respond_to?( symbol, include_private = false )
532
+ begin
533
+ dispatch = MethodDispatch.new( symbol )
534
+ rescue NoMethodError
535
+ super
536
+ end
537
+ end
538
+
539
+ class Cache #:nodoc:
540
+ def initialize( dbBridge )
541
+ @dbBridge = dbBridge
542
+ @objects = {}
543
+ @collections_by_query = {}
544
+ @commit_times = {}
545
+ end
546
+
547
+ def commit( db_object )
548
+ committer = Committer.new db_object, @dbBridge, self
549
+ committer.execute
550
+ end
551
+
552
+ # Flushes a domain object.
553
+ def flush(db_object)
554
+ hash_by_domain_class( db_object.domain_class ).delete db_object.pk_id
555
+ flush_collection_cache( db_object.domain_class )
556
+ end
557
+
558
+ def flush_collection_cache( domain_class )
559
+ @collections_by_query.keys.each { |query|
560
+ if query.domain_class == domain_class
561
+ @collections_by_query.delete( query )
562
+ end
563
+ }
564
+ end
565
+
566
+ # Returns a cached domain object, or nil if none is found.
567
+ def get( domain_class, pk_id )
568
+ hash_by_domain_class( domain_class )[pk_id].clone
569
+ end
570
+
571
+ # Returns an array of all domain objects of a given type.
572
+ def get_all( domain_class )
573
+ hash_by_domain_class( domain_class ).values.collect { |d_obj|
574
+ d_obj.clone
575
+ }
576
+ end
577
+
578
+ def get_by_query( query )
579
+ unless @collections_by_query[query]
580
+ superset_query, pk_ids =
581
+ @collections_by_query.find { |other_query, pk_ids|
582
+ query.implies?( other_query )
583
+ }
584
+ if pk_ids
585
+ collection = ( pk_ids.collect { |pk_id|
586
+ get( query.domain_class, pk_id )
587
+ } ).select { |dobj| query.object_meets( dobj ) }.collect { |dobj|
588
+ dobj.pk_id
589
+ }
590
+ collection = collection[query.limit] if query.limit
591
+ if ( order_by = query.order_by )
592
+ collection = collection.sort_by { |pk_id|
593
+ get( query.domain_class, pk_id ).send( order_by )
594
+ }
595
+ collection.reverse! if query.order_by_order == Query::DESC
596
+ else
597
+ collection = collection.sort
598
+ end
599
+ @collections_by_query[query] = collection
600
+ elsif @collections_by_query.values
601
+ newObjects = @dbBridge.get_collection_by_query(query)
602
+ newObjects.each { |dbObj| save dbObj }
603
+ @collections_by_query[query] = newObjects.collect { |dobj|
604
+ dobj.pk_id
605
+ }
606
+ end
607
+ end
608
+ collection = []
609
+ @collections_by_query[query].each { |pk_id|
610
+ dobj = get( query.domain_class, pk_id )
611
+ collection << dobj if dobj
612
+ }
613
+ collection
614
+ end
615
+
616
+ def hash_by_domain_class( domain_class )
617
+ unless @objects[domain_class]
618
+ @objects[domain_class] = {}
619
+ end
620
+ @objects[domain_class]
621
+ end
622
+
623
+ def last_commit_time( domain_class, pk_id )
624
+ by_domain_class = @commit_times[domain_class]
625
+ by_domain_class ? by_domain_class[pk_id] : nil
626
+ end
627
+
628
+ def set_commit_time( d_obj )
629
+ by_domain_class = @commit_times[d_obj.domain_class]
630
+ if by_domain_class.nil?
631
+ by_domain_class = {}
632
+ @commit_times[d_obj.domain_class] = by_domain_class
633
+ end
634
+ by_domain_class[d_obj.pk_id] = Time.now
635
+ end
636
+
637
+ # Saves a domain object.
638
+ def save(db_object)
639
+ hash = hash_by_domain_class( db_object.domain_class )
640
+ hash[db_object.pk_id] = db_object
641
+ flush_collection_cache( db_object.domain_class )
642
+ end
643
+
644
+ def update_after_commit( committer ) #:nodoc:
645
+ if committer.commit_type == Committer::UPDATE ||
646
+ committer.commit_type == Committer::INSERT
647
+ save( committer.db_object )
648
+ elsif committer.commit_type == Committer::DELETE
649
+ flush( committer.db_object )
650
+ end
651
+ set_commit_time( committer.db_object )
652
+ end
653
+ end
654
+
655
+ class MethodDispatch #:nodoc:
656
+ attr_reader :symbol, :args
657
+
658
+ def initialize( orig_method, *other_args )
659
+ @orig_method = orig_method
660
+ @orig_args = other_args
661
+ if @orig_args.size > 0
662
+ @maybe_proc = @orig_args.shift
663
+ end
664
+ @methodName = orig_method.id2name
665
+ if @methodName =~ /^get(.*)$/
666
+ dispatch_get_method
667
+ else
668
+ raise_no_method_error
669
+ end
670
+ end
671
+
672
+ def dispatch_get_plural
673
+ if @orig_args.size == 0 && @maybe_proc.nil?
674
+ @symbol = :get_all
675
+ @args = [ @domain_class ]
676
+ else
677
+ searchTerm = @orig_args[0]
678
+ fieldName = @orig_args[1]
679
+ if searchTerm.nil? && @maybe_proc.nil? && fieldName.nil?
680
+ msg = "ObjectStore\##{ @orig_method } needs a field name as its " +
681
+ "second argument if its first argument is nil"
682
+ raise( ArgumentError, msg, caller )
683
+ end
684
+ dispatch_get_plural_by_query_block_or_search_term( searchTerm,
685
+ fieldName )
686
+ end
687
+ end
688
+
689
+ def dispatch_get_plural_by_query_block
690
+ inferrer = Query::Inferrer.new( @domain_class ) { |obj|
691
+ @maybe_proc.call( obj )
692
+ }
693
+ @symbol = :get_subset
694
+ @args = [ inferrer.execute ]
695
+ end
696
+
697
+ def dispatch_get_plural_by_query_block_or_search_term( searchTerm,
698
+ fieldName )
699
+ if !@maybe_proc.nil? && searchTerm.nil?
700
+ dispatch_get_plural_by_query_block
701
+ elsif @maybe_proc.nil? && ( !( searchTerm.nil? && fieldName.nil? ) )
702
+ @symbol = :get_filtered
703
+ @args = [ @domain_class.name, searchTerm, fieldName ]
704
+ else
705
+ raise( ArgumentError,
706
+ "Shouldn't send both a query block and a search term",
707
+ caller )
708
+ end
709
+ end
710
+
711
+ def dispatch_get_method
712
+ begin
713
+ dispatch_get_singular
714
+ rescue CouldntMatchDomainClassError
715
+ domain_class_name = camel_case_method_name_after_get.singular
716
+ begin
717
+ @domain_class =
718
+ DomainObject.get_domain_class_from_string( domain_class_name )
719
+ dispatch_get_plural
720
+ rescue CouldntMatchDomainClassError
721
+ raise_no_method_error
722
+ end
723
+ end
724
+ end
725
+
726
+ def dispatch_get_singular
727
+ domain_class = DomainObject.get_domain_class_from_string(
728
+ camel_case_method_name_after_get
729
+ )
730
+ if @orig_args[0].class <= Integer
731
+ @symbol = :get
732
+ @args = [ domain_class, @orig_args[0] ]
733
+ elsif @orig_args[0].class <= DomainObject
734
+ @symbol = :get_map_object
735
+ @args = [ domain_class, @orig_args[0], @orig_args[1] ]
736
+ end
737
+ end
738
+
739
+ def camel_case_method_name_after_get
740
+ @orig_method.id2name =~ /^get(.*)$/
741
+ $1.underscore_to_camel_case
742
+ end
743
+
744
+ def raise_no_method_error
745
+ raise( NoMethodError, "undefined method '#{ @methodName }'", caller )
746
+ end
747
+ end
748
+ end
749
+
750
+ class SqlValueConverter #:nodoc:
751
+ attr_reader :domain_class, :row_hash
752
+
753
+ def initialize( domain_class, row_hash )
754
+ @domain_class = domain_class
755
+ @row_hash = row_hash
756
+ end
757
+
758
+ def []( key )
759
+ begin
760
+ field = @domain_class.get_field( key )
761
+ val = field.value_from_sql( @row_hash[ field.db_field_name ] )
762
+ if field.instance_of?( PrimaryKeyField ) && val.nil?
763
+ raise FieldMatchError, error_msg, caller
764
+ else
765
+ val
766
+ end
767
+ rescue MissingError
768
+ nil
769
+ end
770
+ end
771
+
772
+ def error_msg
773
+ "The field \"" + @domain_class.sql_primary_key_name +
774
+ "\" can\'t be found in the table \"" +
775
+ @domain_class.table_name + "\"."
776
+ end
777
+ end
778
+ end