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
@@ -1,18 +1,630 @@
1
- require 'lafcadio/includer'
2
- Includer.include( 'objectStore' )
3
- require 'lafcadio/util/English'
1
+ require 'lafcadio/domain'
2
+ require 'lafcadio/query'
3
+ require 'lafcadio/util'
4
4
 
5
5
  module Lafcadio
6
+ class Committer #:nodoc:
7
+ INSERT = 1
8
+ UPDATE = 2
9
+ DELETE = 3
10
+
11
+ attr_reader :commit_type, :db_object
12
+
13
+ def initialize(db_object, dbBridge)
14
+ @db_object = db_object
15
+ @dbBridge = dbBridge
16
+ @objectStore = ObjectStore.get_object_store
17
+ @commit_type = nil
18
+ end
19
+
20
+ def execute
21
+ @db_object.verify if LafcadioConfig.new()['checkFields'] == 'onCommit'
22
+ set_commit_type
23
+ @db_object.last_commit = get_last_commit
24
+ @db_object.pre_commit_trigger
25
+ update_dependent_domain_objects if @db_object.delete
26
+ @dbBridge.commit @db_object
27
+ unless @db_object.pk_id
28
+ @db_object.pk_id = @dbBridge.last_pk_id_inserted
29
+ end
30
+ @db_object.post_commit_trigger
31
+ end
32
+
33
+ def get_last_commit
34
+ if @db_object.delete
35
+ DomainObject::COMMIT_DELETE
36
+ elsif @db_object.pk_id
37
+ DomainObject::COMMIT_EDIT
38
+ else
39
+ DomainObject::COMMIT_ADD
40
+ end
41
+ end
42
+
43
+ def set_commit_type
44
+ if @db_object.delete
45
+ @commit_type = DELETE
46
+ elsif @db_object.pk_id
47
+ @commit_type = UPDATE
48
+ else
49
+ @commit_type = INSERT
50
+ end
51
+ end
52
+
53
+ def update_dependent_domain_objects
54
+ dependent_classes = @db_object.object_type.dependent_classes
55
+ dependent_classes.keys.each { |aClass|
56
+ field = dependent_classes[aClass]
57
+ collection = @objectStore.get_filtered( aClass.name, @db_object,
58
+ field.name )
59
+ collection.each { |dependentObject|
60
+ if field.delete_cascade
61
+ dependentObject.delete = true
62
+ else
63
+ dependentObject.send( field.name + '=', nil )
64
+ end
65
+ @objectStore.commit(dependentObject)
66
+ }
67
+ }
68
+ end
69
+ end
70
+
71
+ class CouldntMatchObjectTypeError < RuntimeError #:nodoc:
72
+ end
73
+
74
+ class DbBridge #:nodoc:
75
+ @@dbh = nil
76
+ @@last_pk_id_inserted = nil
77
+
78
+ def self._load(aString)
79
+ aString =~ /dbh:/
80
+ dbString = $'
81
+ begin
82
+ dbh = Marshal.load(dbString)
83
+ rescue TypeError
84
+ dbh = nil
85
+ end
86
+ new dbh
87
+ end
88
+
89
+ def initialize
90
+ @db_conn = DbConnection.get_db_connection
91
+ ObjectSpace.define_finalizer( self, proc { |id|
92
+ DbConnection.get_db_connection.disconnect
93
+ } )
94
+ end
95
+
96
+ def _dump(aDepth)
97
+ dbDump = @dbh.respond_to?( '_dump' ) ? @dbh._dump : @dbh.class.to_s
98
+ "dbh:#{dbDump}"
99
+ end
100
+
101
+ def commit(db_object)
102
+ sqlMaker = DomainObjectSqlMaker.new(db_object)
103
+ sqlMaker.sql_statements.each { |sql, binds| execute_commit( sql, binds ) }
104
+ if sqlMaker.sql_statements[0].first =~ /insert/
105
+ sql = 'select last_insert_id()'
106
+ result = execute_select( sql )
107
+ @@last_pk_id_inserted = result[0]['last_insert_id()'].to_i
108
+ end
109
+ end
110
+
111
+ def execute_commit( sql, binds ); @db_conn.do( sql, *binds ); end
112
+
113
+ def execute_select(sql)
114
+ maybe_log sql
115
+ begin
116
+ @db_conn.select_all( sql )
117
+ rescue DBI::DatabaseError => e
118
+ raise $!.to_s + ": #{ e.errstr }"
119
+ end
120
+ end
121
+
122
+ def get_collection_by_query(query)
123
+ object_type = query.object_type
124
+ execute_select( query.to_sql ).collect { |row_hash|
125
+ object_type.new( SqlValueConverter.new( object_type, row_hash ) )
126
+ }
127
+ end
128
+
129
+ def group_query( query )
130
+ execute_select( query.to_sql )[0].collect { |val|
131
+ if query.field_name != query.object_type.sql_primary_key_name
132
+ a_field = query.object_type.get_field( query.field_name )
133
+ a_field.value_from_sql( val )
134
+ else
135
+ val.to_i
136
+ end
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
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 LinkFields. In terms of +object_type+ and
209
+ # +pk_id+, a DomainObjectProxy instance looks to the outside world like the
210
+ # domain object it's supposed to represent. It only retrieves its domain
211
+ # object from the database when member data is requested.
212
+ #
213
+ # In normal usage you will probably never manipulate a DomainObjectProxy
214
+ # directly, but you may discover it by accident by calling
215
+ # DomainObjectProxy#class (or DomainObject#class) instead of
216
+ # DomainObjectProxy#object_type (or DomainObjectProxy#object_type).
217
+ class DomainObjectProxy
218
+ include DomainComparable
219
+
220
+ attr_accessor :object_type, :pk_id
221
+
222
+ def initialize(object_typeOrDbObject, pk_id = nil)
223
+ if pk_id
224
+ @object_type = object_typeOrDbObject
225
+ @pk_id = pk_id
226
+ elsif object_typeOrDbObject.class < DomainObject
227
+ @db_object = object_typeOrDbObject
228
+ @d_obj_retrieve_time = Time.now
229
+ @object_type = @db_object.class
230
+ @pk_id = @db_object.pk_id
231
+ else
232
+ raise ArgumentError
233
+ end
234
+ @db_object = nil
235
+ end
236
+
237
+ def get_db_object
238
+ object_store = ObjectStore.get_object_store
239
+ if @db_object.nil? || needs_refresh?
240
+ @db_object = object_store.get(@object_type, @pk_id)
241
+ @d_obj_retrieve_time = Time.now
242
+
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( @object_type, @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(object_type)
272
+ "delete from #{ object_type.table_name} " +
273
+ "where #{ object_type.sql_primary_key_name }=#{ @obj.pk_id }"
274
+ end
275
+
276
+ def get_name_value_pairs(object_type)
277
+ nameValues = []
278
+ object_type.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(object_type)
294
+ fields = object_type.class_fields
295
+ nameValuePairs = get_name_value_pairs(object_type)
296
+ if object_type.is_based_on?
297
+ nameValuePairs[object_type.sql_primary_key_name] = 'LAST_INSERT_ID()'
298
+ end
299
+ fieldNameStr = nameValuePairs.keys.join ", "
300
+ fieldValueStr = nameValuePairs.values.join ", "
301
+ "insert into #{ object_type.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 { |object_type|
311
+ statements << statement_bind_value_pair( object_type )
312
+ }
313
+ statements.reverse
314
+ end
315
+
316
+ def statement_bind_value_pair( object_type )
317
+ @bind_values = []
318
+ if @obj.pk_id == nil
319
+ statement = insert_sql(object_type)
320
+ else
321
+ if @obj.delete
322
+ statement = delete_sql(object_type)
323
+ else
324
+ statement = update_sql(object_type)
325
+ end
326
+ end
327
+ [statement, @bind_values]
328
+ end
329
+
330
+ def update_sql(object_type)
331
+ nameValueStrings = []
332
+ nameValuePairs = get_name_value_pairs(object_type)
333
+ nameValuePairs.each { |key, value|
334
+ nameValueStrings << "#{key}=#{ value }"
335
+ }
336
+ allNameValues = nameValueStrings.join ', '
337
+ "update #{ object_type.table_name} set #{allNameValues} " +
338
+ "where #{ object_type.sql_primary_key_name}=#{@obj.pk_id}"
339
+ end
340
+ end
341
+
6
342
  class FieldMatchError < StandardError; end
7
343
 
8
- class ObjectStore
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
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(object_type, pk_id)
431
+ query = Query.new object_type, pk_id
432
+ @cache.get_by_query( query )[0] ||
433
+ ( raise( DomainObjectNotFoundError,
434
+ "Can't find #{object_type} #{pk_id}", caller ) )
435
+ end
436
+
437
+ # Returns all domain objects for the given domain class.
438
+ def get_all(object_type); @cache.get_by_query( Query.new( object_type ) ); 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.object_type.bare_name.decapitalize
446
+ end
447
+
448
+ def get_filtered(object_typeName, searchTerm, fieldName = nil) #:nodoc:
449
+ object_type = DomainObject.get_object_type_from_string object_typeName
450
+ fieldName = get_field_name( searchTerm ) unless fieldName
451
+ if searchTerm.class <= DomainObject
452
+ cond_class = Query::Link
453
+ else
454
+ cond_class = Query::Equals
455
+ end
456
+ get_subset( cond_class.new( fieldName, searchTerm, object_type ) )
457
+ end
458
+
459
+ def get_map_match(object_type, mapped) #:nodoc:
460
+ Query::Equals.new( get_field_name( mapped ), mapped, object_type )
461
+ end
462
+
463
+ def get_map_object(object_type, map1, map2) #:nodoc:
464
+ unless map1 && map2
465
+ raise ArgumentError,
466
+ "ObjectStore#get_map_object needs two non-nil keys", caller
467
+ end
468
+ mapMatch1 = get_map_match object_type, map1
469
+ mapMatch2 = get_map_match object_type, map2
470
+ condition = Query::CompoundCondition.new mapMatch1, mapMatch2
471
+ get_subset(condition)[0]
472
+ end
473
+
474
+ def get_mapped(searchTerm, resultTypeName) #:nodoc:
475
+ resultType = DomainObject.get_object_type_from_string resultTypeName
476
+ firstTypeName = searchTerm.class.bare_name
477
+ secondTypeName = resultType.bare_name
478
+ mapTypeName = firstTypeName + secondTypeName
479
+ get_filtered( mapTypeName, searchTerm ).collect { |mapObj|
480
+ mapObj.send( resultType.name.decapitalize )
481
+ }
482
+ end
483
+
484
+ # Retrieves the maximum value across all instances of one domain class.
485
+ # ObjectStore#get_max( Client )
486
+ # returns the highest +pk_id+ in the +clients+ table.
487
+ # ObjectStore#get_max( Invoice, "rate" )
488
+ # will return the highest rate for all invoices.
489
+ def get_max( domain_class, field_name = 'pk_id' )
490
+ @dbBridge.group_query( Query::Max.new( domain_class, field_name ) ).only
491
+ end
492
+
493
+ # Retrieves a collection of domain objects by +pk_id+.
494
+ # ObjectStore#get_objects( Clients, [ 1, 2, 3 ] )
495
+ def get_objects(object_type, pk_ids)
496
+ get_subset Query::In.new('pk_id', pk_ids, object_type)
497
+ end
498
+
499
+ def get_subset(conditionOrQuery) #:nodoc:
500
+ if conditionOrQuery.class <= Query::Condition
501
+ condition = conditionOrQuery
502
+ query = Query.new condition.object_type, condition
503
+ else
504
+ query = conditionOrQuery
505
+ end
506
+ @cache.get_by_query( query )
507
+ end
508
+
509
+ def last_commit_time( domain_class, pk_id ) #:nodoc:
510
+ @cache.last_commit_time( domain_class, pk_id )
511
+ end
512
+
513
+ def method_missing(methodId, *args) #:nodoc:
514
+ proc = block_given? ? ( proc { |obj| yield( obj ) } ) : nil
515
+ dispatch = MethodDispatch.new( methodId, proc, *args )
516
+ self.send( dispatch.symbol, *dispatch.args )
517
+ end
518
+
519
+ def respond_to?( symbol, include_private = false )
520
+ begin
521
+ dispatch = MethodDispatch.new( symbol )
522
+ rescue NoMethodError
523
+ super
524
+ end
525
+ end
526
+
527
+ class Cache #:nodoc:
528
+ def initialize( dbBridge )
529
+ @dbBridge = dbBridge
530
+ @objects = {}
531
+ @collections_by_query = {}
532
+ @commit_times = {}
533
+ end
534
+
535
+ def commit( db_object )
536
+ committer = Committer.new db_object, @dbBridge
537
+ committer.execute
538
+ update_after_commit( committer )
539
+ end
540
+
541
+ # Flushes a domain object.
542
+ def flush(db_object)
543
+ hash_by_object_type(db_object.object_type).delete db_object.pk_id
544
+ flush_collection_cache( db_object.object_type )
545
+ end
546
+
547
+ def flush_collection_cache( object_type )
548
+ @collections_by_query.keys.each { |query|
549
+ if query.object_type == object_type
550
+ @collections_by_query.delete( query )
551
+ end
552
+ }
553
+ end
554
+
555
+ # Returns a cached domain object, or nil if none is found.
556
+ def get(object_type, pk_id)
557
+ hash_by_object_type(object_type)[pk_id].clone
558
+ end
559
+
560
+ # Returns an array of all domain objects of a given type.
561
+ def get_all(object_type)
562
+ hash_by_object_type(object_type).values.collect { |d_obj| d_obj.clone }
563
+ end
564
+
565
+ def get_by_query( query )
566
+ unless @collections_by_query[query]
567
+ newObjects = @dbBridge.get_collection_by_query(query)
568
+ newObjects.each { |dbObj| save dbObj }
569
+ @collections_by_query[query] = newObjects.collect { |dobj|
570
+ dobj.pk_id
571
+ }
572
+ end
573
+ collection = []
574
+ @collections_by_query[query].each { |pk_id|
575
+ dobj = get( query.object_type, pk_id )
576
+ collection << dobj if dobj
577
+ }
578
+ collection
579
+ end
580
+
581
+ def hash_by_object_type(object_type)
582
+ unless @objects[object_type]
583
+ @objects[object_type] = {}
584
+ end
585
+ @objects[object_type]
586
+ end
587
+
588
+ def last_commit_time( domain_class, pk_id )
589
+ by_domain_class = @commit_times[domain_class]
590
+ by_domain_class ? by_domain_class[pk_id] : nil
591
+ end
592
+
593
+ def set_commit_time( d_obj )
594
+ by_domain_class = @commit_times[d_obj.object_type]
595
+ if by_domain_class.nil?
596
+ by_domain_class = {}
597
+ @commit_times[d_obj.object_type] = by_domain_class
598
+ end
599
+ by_domain_class[d_obj.pk_id] = Time.now
600
+ end
601
+
602
+ # Saves a domain object.
603
+ def save(db_object)
604
+ hash_by_object_type(db_object.object_type)[db_object.pk_id] = db_object
605
+ flush_collection_cache( db_object.object_type )
606
+ end
607
+
608
+ def update_after_commit( committer ) #:nodoc:
609
+ if committer.commit_type == Committer::UPDATE ||
610
+ committer.commit_type == Committer::INSERT
611
+ save( committer.db_object )
612
+ elsif committer.commit_type == Committer::DELETE
613
+ flush( committer.db_object )
614
+ end
615
+ set_commit_time( committer.db_object )
616
+ end
617
+ end
618
+
9
619
  class MethodDispatch #:nodoc:
10
620
  attr_reader :symbol, :args
11
621
 
12
- def initialize( orig_method, maybe_proc, *orig_args )
622
+ def initialize( orig_method, *other_args )
13
623
  @orig_method = orig_method
14
- @maybe_proc = maybe_proc
15
- @orig_args = orig_args
624
+ @orig_args = other_args
625
+ if @orig_args.size > 0
626
+ @maybe_proc = @orig_args.shift
627
+ end
16
628
  @methodName = orig_method.id2name
17
629
  if @methodName =~ /^get(.*)$/
18
630
  dispatch_get_method
@@ -23,7 +635,7 @@ module Lafcadio
23
635
 
24
636
  def dispatch_get_plural
25
637
  if @orig_args.size == 0 && @maybe_proc.nil?
26
- @symbol = :getAll
638
+ @symbol = :get_all
27
639
  @args = [ @domain_class ]
28
640
  else
29
641
  searchTerm = @orig_args[0]
@@ -42,7 +654,7 @@ module Lafcadio
42
654
  inferrer = Query::Inferrer.new( @domain_class ) { |obj|
43
655
  @maybe_proc.call( obj )
44
656
  }
45
- @symbol = :getSubset
657
+ @symbol = :get_subset
46
658
  @args = [ inferrer.execute ]
47
659
  end
48
660
 
@@ -51,7 +663,7 @@ module Lafcadio
51
663
  if !@maybe_proc.nil? && searchTerm.nil?
52
664
  dispatch_get_plural_by_query_block
53
665
  elsif @maybe_proc.nil? && ( !( searchTerm.nil? && fieldName.nil? ) )
54
- @symbol = :getFiltered
666
+ @symbol = :get_filtered
55
667
  @args = [ @domain_class.name, searchTerm, fieldName ]
56
668
  else
57
669
  raise( ArgumentError,
@@ -64,10 +676,11 @@ module Lafcadio
64
676
  begin
65
677
  dispatch_get_singular
66
678
  rescue CouldntMatchObjectTypeError
67
- objectTypeName = English.singular( method_name_after_get )
679
+ object_typeName = English.singular( camel_case_method_name_after_get
680
+ )
68
681
  begin
69
682
  @domain_class = DomainObject.
70
- getObjectTypeFromString( objectTypeName )
683
+ get_object_type_from_string( object_typeName )
71
684
  dispatch_get_plural
72
685
  rescue CouldntMatchObjectTypeError
73
686
  raise_no_method_error
@@ -76,20 +689,21 @@ module Lafcadio
76
689
  end
77
690
 
78
691
  def dispatch_get_singular
79
- objectType = DomainObject.
80
- getObjectTypeFromString( method_name_after_get )
692
+ object_type = DomainObject.get_object_type_from_string(
693
+ camel_case_method_name_after_get
694
+ )
81
695
  if @orig_args[0].class <= Integer
82
696
  @symbol = :get
83
- @args = [ objectType, @orig_args[0] ]
697
+ @args = [ object_type, @orig_args[0] ]
84
698
  elsif @orig_args[0].class <= DomainObject
85
- @symbol = :getMapObject
86
- @args = [ objectType, @orig_args[0], @orig_args[1] ]
699
+ @symbol = :get_map_object
700
+ @args = [ object_type, @orig_args[0], @orig_args[1] ]
87
701
  end
88
702
  end
89
703
 
90
- def method_name_after_get
704
+ def camel_case_method_name_after_get
91
705
  @orig_method.id2name =~ /^get(.*)$/
92
- $1
706
+ $1.underscore_to_camel_case
93
707
  end
94
708
 
95
709
  def raise_no_method_error
@@ -97,4 +711,33 @@ module Lafcadio
97
711
  end
98
712
  end
99
713
  end
714
+
715
+ class SqlValueConverter #:nodoc:
716
+ attr_reader :object_type, :row_hash
717
+
718
+ def initialize(object_type, row_hash)
719
+ @object_type = object_type
720
+ @row_hash = row_hash
721
+ end
722
+
723
+ def []( key )
724
+ begin
725
+ field = @object_type.get_field( key )
726
+ val = field.value_from_sql( @row_hash[ field.db_field_name ] )
727
+ if field.instance_of?( PrimaryKeyField ) && val.nil?
728
+ raise FieldMatchError, error_msg, caller
729
+ else
730
+ val
731
+ end
732
+ rescue MissingError
733
+ nil
734
+ end
735
+ end
736
+
737
+ def error_msg
738
+ "The field \"" + @object_type.sql_primary_key_name +
739
+ "\" can\'t be found in the table \"" +
740
+ @object_type.table_name + "\"."
741
+ end
742
+ end
100
743
  end