lafcadio 0.4.3 → 0.5.2

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