lafcadio 0.6.1 → 0.6.2

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