lafcadio 0.9.2 → 0.9.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -14,27 +14,41 @@ module Lafcadio
14
14
  # dbname:lafcadio_test
15
15
  # dbhost:localhost
16
16
  class LafcadioConfig < Hash
17
- @@filename = nil
18
- @@value_hash = nil
19
-
20
- def self.set_filename(filename); @@filename = filename; end
17
+ @@value_hash = {}
18
+ @@instances = []
21
19
 
22
- def self.set_values( value_hash ); @@value_hash = value_hash; end
23
-
24
- def initialize
25
- if @@value_hash
26
- @@value_hash.each { |key, value| self[key] = value }
27
- elsif @@filename
28
- File.new( @@filename ).each_line { |line|
20
+ def self.[]=( k, v )
21
+ @@value_hash[k] = v
22
+ @@instances.each do |instance| instance[k] = v; end
23
+ end
24
+
25
+ def self.new
26
+ inst = super
27
+ @@instances << inst
28
+ inst
29
+ end
30
+
31
+ def self.set_filename(filename)
32
+ @@value_hash = {}
33
+ if filename
34
+ File.new( filename ).each_line { |line|
29
35
  line.chomp =~ /^(.*?):(.*)$/
30
36
  self[$1] = $2
31
37
  }
32
38
  end
33
39
  end
34
- end
40
+
41
+ def self.set_values( value_hash )
42
+ @@value_hash = ( value_hash.nil? ? {} : value_hash )
43
+ ObjectStore.db_type = @@value_hash['dbtype'] if @@value_hash['dbtype']
44
+ end
35
45
 
36
- class MissingError < RuntimeError
46
+ def initialize
47
+ @@value_hash.each { |key, value| self[key] = value }
48
+ end
37
49
  end
50
+
51
+ class MissingError < RuntimeError; end
38
52
  end
39
53
 
40
54
  class String
@@ -47,36 +61,6 @@ class String
47
61
 
48
62
  end
49
63
 
50
- # Returns the number of times that <tt>regexp</tt> occurs in the string.
51
- def count_occurrences(regexp)
52
- count = 0
53
- str = self.clone
54
- while str =~ regexp
55
- count += 1
56
- str = $'
57
- end
58
- count
59
- end
60
-
61
- # Decapitalizes the first letter of the string, or decapitalizes the
62
- # entire string if it's all capitals.
63
- #
64
- # 'InternalClient'.decapitalize -> "internalClient"
65
- # 'SKU'.decapitalize -> "sku"
66
- def decapitalize
67
- string = clone
68
- firstLetter = string[0..0].downcase
69
- string = firstLetter + string[1..string.length]
70
- newString = ""
71
- while string =~ /([A-Z])([^a-z]|$)/
72
- newString += $`
73
- newString += $1.downcase
74
- string = $2 + $'
75
- end
76
- newString += string
77
- newString
78
- end
79
-
80
64
  # Left-pads a string with +fillChar+ up to +size+ size.
81
65
  #
82
66
  # "a".pad( 10, "+") -> "+++++++++a"
metadata CHANGED
@@ -3,15 +3,15 @@ rubygems_version: 0.8.11
3
3
  specification_version: 1
4
4
  name: lafcadio
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.9.2
7
- date: 2006-02-12 00:00:00 -05:00
6
+ version: 0.9.3
7
+ date: 2006-03-21 00:00:00 -08:00
8
8
  summary: Lafcadio is an object-relational mapping layer
9
9
  require_paths:
10
10
  - lib
11
11
  email: sera@fhwang.net
12
12
  homepage: http://lafcadio.rubyforge.org/
13
13
  rubyforge_project:
14
- description: Lafcadio is an object-relational mapping layer for Ruby and MySQL. It lets you treat database rows like first-class Ruby objects, minimizing the amount of time you have to spend thinking about MySQL vagaries so you can spend more time thinking about your program's logic. It also offers extensive support for unit-testing complex database logic without running cumbersome setup scripts.
14
+ description: Lafcadio is an object-relational mapping layer for Ruby. It lets you treat database rows like first-class Ruby objects, minimizing the amount of time you have to spend thinking about database vagaries so you can spend more time thinking about your program's logic. It also offers extensive support for unit-testing complex database logic without running cumbersome setup scripts. It currently supports MySQL and PostgreSQL.
15
15
  autorequire: lafcadio
16
16
  default_executable:
17
17
  bindir: bin
@@ -31,9 +31,7 @@ files:
31
31
  - lib/lafcadio
32
32
  - lib/lafcadio.rb
33
33
  - lib/lafcadio.rb~
34
- - lib/lafcadio/dateTime.rb~
35
34
  - lib/lafcadio/depend.rb
36
- - lib/lafcadio/depend.rb~
37
35
  - lib/lafcadio/domain.rb
38
36
  - lib/lafcadio/domain.rb~
39
37
  - lib/lafcadio/mock.rb
@@ -41,7 +39,6 @@ files:
41
39
  - lib/lafcadio/objectField.rb
42
40
  - lib/lafcadio/objectField.rb~
43
41
  - lib/lafcadio/objectStore.rb
44
- - lib/lafcadio/objectStore.rb.~1.64.~
45
42
  - lib/lafcadio/objectStore.rb~
46
43
  - lib/lafcadio/query.rb
47
44
  - lib/lafcadio/query.rb~
@@ -53,7 +50,6 @@ files:
53
50
  - lib/lafcadio/util.rb
54
51
  - lib/lafcadio/util.rb~
55
52
  - lib/lafcadio/test/testconfig.dat
56
- - lib/lafcadio/test/testconfig.dat~
57
53
  test_files: []
58
54
 
59
55
  rdoc_options: []
@@ -1,93 +0,0 @@
1
- module Lafcadio
2
- # Represents a specific month in time. With the exception of
3
- # Month.month_names (which returns a zero-based array), every usage of the
4
- # month value assumes that 1 equals January and 12 equals December.
5
- class Month
6
- # Returns an array of the full names of months (in English). Note that
7
- # "January" is the 0th element, and "December" is the 11th element.
8
- def Month.month_names
9
- [ "January", "February", "March", "April", "May", "June", "July",
10
- "August", "September", "October", "November", "December" ]
11
- end
12
-
13
- include Comparable
14
-
15
- attr_reader :month, :year
16
-
17
- # A new month can be set to a specific +month+ and +year+, or you can call
18
- # Month.new with no arguments to receive the current month.
19
- def initialize( year = nil, month = nil )
20
- require 'date'
21
- if month.nil? || year.nil?
22
- date = Date.today
23
- month = date.mon unless month
24
- year = date.year unless year
25
- end
26
- fail "invalid month" if month < 1 || month > 12
27
- @month = month
28
- @year = year
29
- end
30
-
31
- # Returns a new Month that is +amountToAdd+ months later.
32
- def +( amountToAdd )
33
- ( fullYears, remainingMonths ) = amountToAdd.divmod( 12 )
34
- resultYear = @year + fullYears
35
- resultMonth = @month + remainingMonths
36
- if resultMonth > 12
37
- resultMonth -= 12
38
- resultYear += 1
39
- end
40
- Month.new( resultYear, resultMonth )
41
- end
42
-
43
- # Returns a new Month that is +amountToSubtract+ months earlier.
44
- def -(amountToSubtract)
45
- self + (-amountToSubtract)
46
- end
47
-
48
- # Compare this Month to another Month.
49
- def <=>(anOther)
50
- if @year == anOther.year
51
- @month <=> anOther.month
52
- else
53
- @year <=> anOther.year
54
- end
55
- end
56
-
57
- # Returns the last Date of the month.
58
- def end_date
59
- self.next.start_date - 1
60
- end
61
-
62
- # Is this Month equal to +anOther+? +anOther+ must be another Month of the
63
- # same value.
64
- def eql?(anOther)
65
- self == anOther
66
- end
67
-
68
- # Calculate a hash value for this Month.
69
- def hash
70
- "#{@year}#{@month}".to_i
71
- end
72
-
73
- # Returns the next Month.
74
- def next
75
- self + 1
76
- end
77
-
78
- # Returns the previous Month.
79
- def prev
80
- self - 1
81
- end
82
-
83
- # Returns the first Date of the month.
84
- def start_date
85
- Date.new( @year, @month, 1 )
86
- end
87
-
88
- # Returns a string of the format "January 2001".
89
- def to_s
90
- Month.month_names[@month-1][0..2] + " " + @year.to_s
91
- end
92
- end
93
- end
@@ -1,8 +0,0 @@
1
- require 'dbi'
2
- require 'englishext'
3
- require 'extensions/all'
4
- require 'extensions/module'
5
- require 'log4r'
6
- require 'month'
7
- require 'queuehash'
8
- require 'uscommerce'
@@ -1,766 +0,0 @@
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
- superset_query, pk_ids =
577
- @collections_by_query.find { |other_query, pk_ids|
578
- query.implies?( other_query )
579
- }
580
- if pk_ids
581
- @collections_by_query[query] = ( pk_ids.collect { |pk_id|
582
- get( query.domain_class, pk_id )
583
- } ).select { |dobj| query.object_meets( dobj ) }.collect { |dobj|
584
- dobj.pk_id
585
- }
586
- elsif @collections_by_query.values
587
- newObjects = @dbBridge.get_collection_by_query(query)
588
- newObjects.each { |dbObj| save dbObj }
589
- @collections_by_query[query] = newObjects.collect { |dobj|
590
- dobj.pk_id
591
- }
592
- end
593
- end
594
- collection = []
595
- @collections_by_query[query].each { |pk_id|
596
- dobj = get( query.domain_class, pk_id )
597
- collection << dobj if dobj
598
- }
599
- collection
600
- end
601
-
602
- def hash_by_domain_class( domain_class )
603
- unless @objects[domain_class]
604
- @objects[domain_class] = {}
605
- end
606
- @objects[domain_class]
607
- end
608
-
609
- def last_commit_time( domain_class, pk_id )
610
- by_domain_class = @commit_times[domain_class]
611
- by_domain_class ? by_domain_class[pk_id] : nil
612
- end
613
-
614
- def set_commit_time( d_obj )
615
- by_domain_class = @commit_times[d_obj.domain_class]
616
- if by_domain_class.nil?
617
- by_domain_class = {}
618
- @commit_times[d_obj.domain_class] = by_domain_class
619
- end
620
- by_domain_class[d_obj.pk_id] = Time.now
621
- end
622
-
623
- # Saves a domain object.
624
- def save(db_object)
625
- hash = hash_by_domain_class( db_object.domain_class )
626
- hash[db_object.pk_id] = db_object
627
- flush_collection_cache( db_object.domain_class )
628
- end
629
-
630
- def update_after_commit( committer ) #:nodoc:
631
- if committer.commit_type == Committer::UPDATE ||
632
- committer.commit_type == Committer::INSERT
633
- save( committer.db_object )
634
- elsif committer.commit_type == Committer::DELETE
635
- flush( committer.db_object )
636
- end
637
- set_commit_time( committer.db_object )
638
- end
639
- end
640
-
641
- class MethodDispatch #:nodoc:
642
- attr_reader :symbol, :args
643
-
644
- def initialize( orig_method, *other_args )
645
- @orig_method = orig_method
646
- @orig_args = other_args
647
- if @orig_args.size > 0
648
- @maybe_proc = @orig_args.shift
649
- end
650
- @methodName = orig_method.id2name
651
- if @methodName =~ /^get(.*)$/
652
- dispatch_get_method
653
- else
654
- raise_no_method_error
655
- end
656
- end
657
-
658
- def dispatch_get_plural
659
- if @orig_args.size == 0 && @maybe_proc.nil?
660
- @symbol = :get_all
661
- @args = [ @domain_class ]
662
- else
663
- searchTerm = @orig_args[0]
664
- fieldName = @orig_args[1]
665
- if searchTerm.nil? && @maybe_proc.nil? && fieldName.nil?
666
- msg = "ObjectStore\##{ @orig_method } needs a field name as its " +
667
- "second argument if its first argument is nil"
668
- raise( ArgumentError, msg, caller )
669
- end
670
- dispatch_get_plural_by_query_block_or_search_term( searchTerm,
671
- fieldName )
672
- end
673
- end
674
-
675
- def dispatch_get_plural_by_query_block
676
- inferrer = Query::Inferrer.new( @domain_class ) { |obj|
677
- @maybe_proc.call( obj )
678
- }
679
- @symbol = :get_subset
680
- @args = [ inferrer.execute ]
681
- end
682
-
683
- def dispatch_get_plural_by_query_block_or_search_term( searchTerm,
684
- fieldName )
685
- if !@maybe_proc.nil? && searchTerm.nil?
686
- dispatch_get_plural_by_query_block
687
- elsif @maybe_proc.nil? && ( !( searchTerm.nil? && fieldName.nil? ) )
688
- @symbol = :get_filtered
689
- @args = [ @domain_class.name, searchTerm, fieldName ]
690
- else
691
- raise( ArgumentError,
692
- "Shouldn't send both a query block and a search term",
693
- caller )
694
- end
695
- end
696
-
697
- def dispatch_get_method
698
- begin
699
- dispatch_get_singular
700
- rescue CouldntMatchDomainClassError
701
- domain_class_name = English.singular(
702
- camel_case_method_name_after_get
703
- )
704
- begin
705
- @domain_class =
706
- DomainObject.get_domain_class_from_string( domain_class_name )
707
- dispatch_get_plural
708
- rescue CouldntMatchDomainClassError
709
- raise_no_method_error
710
- end
711
- end
712
- end
713
-
714
- def dispatch_get_singular
715
- domain_class = DomainObject.get_domain_class_from_string(
716
- camel_case_method_name_after_get
717
- )
718
- if @orig_args[0].class <= Integer
719
- @symbol = :get
720
- @args = [ domain_class, @orig_args[0] ]
721
- elsif @orig_args[0].class <= DomainObject
722
- @symbol = :get_map_object
723
- @args = [ domain_class, @orig_args[0], @orig_args[1] ]
724
- end
725
- end
726
-
727
- def camel_case_method_name_after_get
728
- @orig_method.id2name =~ /^get(.*)$/
729
- $1.underscore_to_camel_case
730
- end
731
-
732
- def raise_no_method_error
733
- raise( NoMethodError, "undefined method '#{ @methodName }'", caller )
734
- end
735
- end
736
- end
737
-
738
- class SqlValueConverter #:nodoc:
739
- attr_reader :domain_class, :row_hash
740
-
741
- def initialize( domain_class, row_hash )
742
- @domain_class = domain_class
743
- @row_hash = row_hash
744
- end
745
-
746
- def []( key )
747
- begin
748
- field = @domain_class.get_field( key )
749
- val = field.value_from_sql( @row_hash[ field.db_field_name ] )
750
- if field.instance_of?( PrimaryKeyField ) && val.nil?
751
- raise FieldMatchError, error_msg, caller
752
- else
753
- val
754
- end
755
- rescue MissingError
756
- nil
757
- end
758
- end
759
-
760
- def error_msg
761
- "The field \"" + @domain_class.sql_primary_key_name +
762
- "\" can\'t be found in the table \"" +
763
- @domain_class.table_name + "\"."
764
- end
765
- end
766
- end