lafcadio 0.9.0 → 0.9.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -16,7 +16,7 @@
16
16
  # http://lafcadio.rubyforge.org/tutorial.html.
17
17
 
18
18
  module Lafcadio
19
- Version = "0.9.0"
19
+ Version = "0.9.1"
20
20
 
21
21
  require 'lafcadio/depend'
22
22
  require 'lafcadio/domain'
@@ -16,7 +16,7 @@
16
16
  # http://lafcadio.rubyforge.org/tutorial.html.
17
17
 
18
18
  module Lafcadio
19
- Version = "0.8.0"
19
+ Version = "0.9.0"
20
20
 
21
21
  require 'lafcadio/depend'
22
22
  require 'lafcadio/domain'
@@ -17,7 +17,7 @@ module Lafcadio
17
17
  if className != ''
18
18
  fieldClass = Class.by_name( 'Lafcadio::' + className )
19
19
  register_name( name )
20
- field = fieldClass.instantiate_from_xml( @domain_class, fieldElt )
20
+ field = fieldClass.create_from_xml( @domain_class, fieldElt )
21
21
  set_field_attributes( field, fieldElt )
22
22
  else
23
23
  msg = "Couldn't find field class '#{ className }' for field " +
@@ -44,7 +44,7 @@ module Lafcadio
44
44
  fieldAttr = []
45
45
  fieldAttr << FieldAttribute.new( 'size', FieldAttribute::INTEGER )
46
46
  fieldAttr << FieldAttribute.new( 'unique', FieldAttribute::BOOLEAN )
47
- fieldAttr << FieldAttribute.new( 'not_null', FieldAttribute::BOOLEAN )
47
+ fieldAttr << FieldAttribute.new( 'not_nil', FieldAttribute::BOOLEAN )
48
48
  fieldAttr << FieldAttribute.new( 'enum_type', FieldAttribute::ENUM,
49
49
  BooleanField )
50
50
  fieldAttr << FieldAttribute.new( 'enums', FieldAttribute::HASH )
@@ -120,6 +120,8 @@ module Lafcadio
120
120
  class InvalidDataError < ArgumentError; end
121
121
  end
122
122
 
123
+ # DomainComparable is used by DomainObject and DomainObjectProxy to define
124
+ # comparisons.
123
125
  module DomainComparable
124
126
  include Comparable
125
127
 
@@ -137,6 +139,8 @@ module Lafcadio
137
139
  end
138
140
  end
139
141
 
142
+ # Two DomainObjects or DomainObjectProxies are eql? if they have the same
143
+ # +domain_class+ and the same +pk_id+.
140
144
  def eql?( otherObj ); self == otherObj; end
141
145
 
142
146
  def hash; "#{ self.class.name } #{ pk_id }".hash; end
@@ -146,24 +150,37 @@ module Lafcadio
146
150
  # of DomainObject.
147
151
  #
148
152
  # = Defining fields
149
- # There are two ways to define the fields of a DomainObject subclass.
150
- # 1. Defining fields in an XML file. To do this,
151
- # 1. Set one directory to contain all your XML files, by setting
152
- # +classDefinitionDir+ in your LafcadioConfig file.
153
- # 2. Write one XML file per domain class. For example, a User.xml file
154
- # might look like:
155
- # <lafcadio_class_definition name="User">
156
- # <field name="lastName" class="StringField"/>
157
- # <field name="email" class="StringField"/>
158
- # <field name="password" class="StringField"/>
159
- # <field name="birthday" class="DateField"/>
160
- # </lafcadio_class_definition>
161
- # 2. Overriding DomainObject.get_class_fields. The method should return an Array
162
- # of instances of ObjectField or its children. The order is unimportant.
153
+ # There are three ways to define the fields of a DomainObject subclass.
154
+ # 1. <b>One-line field definitions</b>: When defining the class, add fields
155
+ # with class methods like so:
156
+ # class User < Lafcadio::DomainObject
157
+ # string 'lastName'
158
+ # email 'email'
159
+ # string 'password'
160
+ # date 'birthday'
161
+ # end
162
+ # These can also be pluralized:
163
+ # class User < Lafcadio::DomainObject
164
+ # strings 'lastName', 'password'
165
+ # email 'email'
166
+ # date 'birthday'
167
+ # end
168
+ # The class methods you can use are +blob+, +boolean+, +date+,
169
+ # +date_time+, +domain_object+, +email+, +enum+, +float+, +integer+,
170
+ # +month+, +state+, +string+, +text_list+. These correspond to BlobField,
171
+ # BooleanField, DateField, DateTimeField, DomainObjectField, EmailField,
172
+ # EnumField, FloatField, IntegerField, MonthField, StateField,
173
+ # StringField, and TextListField, respectively. Consult their individual
174
+ # RDoc entries for more on how to parameterize them through the class
175
+ # methods.
176
+ # 2. <b>Overriding DomainObject.get_class_fields</b>: The method should
177
+ # return an Array of instances of ObjectField or its children. If you use
178
+ # this method, make sure to call the superclass (this is needed to define
179
+ # the primary key field). The order of fields in the Array is unimportant.
163
180
  # For example:
164
181
  # class User < DomainObject
165
182
  # def User.get_class_fields
166
- # fields = []
183
+ # fields = super
167
184
  # fields << StringField.new(self, 'firstName')
168
185
  # fields << StringField.new(self, 'lastName')
169
186
  # fields << StringField.new(self, 'email')
@@ -172,63 +189,89 @@ module Lafcadio
172
189
  # fields
173
190
  # end
174
191
  # end
192
+ # This method is probably not worth the trouble unless you end up defining
193
+ # your own field classes for some reason.
194
+ # 3. <b>Defining fields in an XML file</b>: Note that this method may be
195
+ # removed in the future. To do this:
196
+ # 1. Set one directory to contain all your XML files, by setting
197
+ # +classDefinitionDir+ in your LafcadioConfig file.
198
+ # 2. Write one XML file per domain class. For example, a User.xml file
199
+ # might look like:
200
+ # <lafcadio_class_definition name="User">
201
+ # <field name="lastName" class="StringField"/>
202
+ # <field name="email" class="StringField"/>
203
+ # <field name="password" class="StringField"/>
204
+ # <field name="birthday" class="DateField"/>
205
+ # </lafcadio_class_definition>
175
206
  #
176
207
  # = Setting and retrieving fields
177
208
  # Once your fields are defined, you can create an instance by passing in a
178
209
  # hash of field names and values.
179
- # john = User.new( 'firstName' => 'John', 'lastName' => 'Doe',
180
- # 'email' => 'john.doe@email.com',
181
- # 'password' => 'my_password',
182
- # 'birthday' => tenYearsAgo )
210
+ #
211
+ # john = User.new(
212
+ # :firstName => 'John', :lastName => 'Doe',
213
+ # :email => 'john.doe@email.com', :password => 'my_password',
214
+ # :birthday => Date.new( 1979, 1, 15 )
215
+ # )
183
216
  #
184
217
  # You can read and write these fields like normal instance attributes.
185
- # john.email => 'john.doe@email.com'
218
+ # john.email # => 'john.doe@email.com'
186
219
  # john.email = 'john.doe@mail.email.com'
187
220
  #
188
221
  # If your domain class has fields that refer to other domain classes, or even
189
- # to another row in the same table, you can use a DomainObjectField to express the
190
- # relation.
191
- # <lafcadio_class_definition name="Message">
192
- # <field name="subject" class="StringField" />
193
- # <field name="body" class="StringField" />
194
- # <field name="author" class="DomainObjectField" linked_type="User" />
195
- # <field name="recipient" class="DomainObjectField" linked_type="User" />
196
- # <field name="dateSent" class="DateField" />
197
- # </lafcadio_class_definition>
198
- #
199
- # msg = Message.new( 'subject' => 'hi there',
200
- # 'body' => 'You wanna go to the movies on Saturday?',
201
- # 'author' => john, 'recipient' => jane,
202
- # 'dateSent' => Date.today )
222
+ # to another row in the same table, you can use a DomainObjectField to
223
+ # express the relation.
224
+ #
225
+ # class Message < Lafcadio::DomainObject
226
+ # strings 'subject', 'body'
227
+ # domain_object User, 'author'
228
+ # domain_object User, 'recipient'
229
+ # date 'date_sent'
230
+ # end
231
+ #
232
+ # msg = Message.new(
233
+ # :subject => 'hi there',
234
+ # :body => 'You wanna go to the movies on Saturday?',
235
+ # :author => john, :recipient => jane, :dateSent => Date.today
236
+ # )
203
237
  #
204
238
  # = pk_id and committing
205
- # Lafcadio requires that each table has a numeric primary key. It assumes that
206
- # this key is named +pk_id+ in the database, though that can be overridden.
239
+ # Lafcadio requires that each table has a numeric primary key. It assumes
240
+ # that this key is named +pk_id+ in the database, though that can be
241
+ # overridden.
207
242
  #
208
- # When you create a domain object by calling new, you should not assign a
209
- # +pk_id+ to the new instance. The pk_id will automatically be set when you
210
- # commit the object by calling DomainObject#commit.
243
+ # When you create a domain object by calling DomainObject.new, you should not
244
+ # assign a +pk_id+ to the new instance. The pk_id will automatically be set
245
+ # when you commit the object by calling DomainObject#commit.
211
246
  #
212
- # However, you may want to manually set +pk_id+ when setting up a test case, so
213
- # you can ensure that a domain object has a given primary key.
247
+ # However, you may want to manually set +pk_id+ when setting up a test case,
248
+ # so you can ensure that a domain object has a given primary key.
214
249
  #
215
250
  # = Naming assumptions, and how to override them
216
251
  # By default, Lafcadio assumes that every domain object is indexed by the
217
252
  # field +pk_id+ in the database schema. If you're dealing with a table that
218
- # uses a different field name, override DomainObject.sql_primary_key_name.
253
+ # uses a different field name, call DomainObject.sql_primary_key_name.
219
254
  # However, you will always use +pk_id+ in your Ruby code.
220
255
  #
221
- # Lafcadio assumes that a domain class corresponds to a table whose name is
222
- # the plural of the class name, and whose first letter is lowercase. A User
223
- # class is assumed to be stored in a "users" table, while a ProductCategory
224
- # class is assumed to be stored in a "productCategories" table. Override
256
+ # Lafcadio assumes that a domain class corresponds to a table whose name is
257
+ # the pluralized, lower-case, underscored version of the class name. A User
258
+ # class is assumed to be stored in a "users" table, while a ProductCategory
259
+ # class is assumed to be stored in a "product_categories" table. Call
225
260
  # DomainObject.table_name to override this behavior.
226
261
  #
262
+ # class LegacyThing < Lafcadio::DomainObject
263
+ # string 'some_field'
264
+ # sql_primary_key_name 'some_legacy_id'
265
+ # table_name 'some_legacy_table'
266
+ # end
267
+ # thing = LegacyThing[9909]
268
+ # thing.pk_id # => 9909
269
+ #
227
270
  # = Inheritance
228
271
  # Domain classes can inherit from other domain classes; they have all the
229
272
  # fields of any concrete superclasses plus any new fields defined for
230
273
  # themselves. You can use normal inheritance to define this:
231
- # class User < DomainObject
274
+ # class User < Lafcadio::DomainObject
232
275
  # ...
233
276
  # end
234
277
  #
@@ -248,28 +291,32 @@ module Lafcadio
248
291
 
249
292
  include DomainComparable
250
293
 
294
+ # Shortcut method for retrieving one domain object.
295
+ #
296
+ # User[7356] # => user with the pk_id 7536
251
297
  def self.[]( pk_id ); get( pk_id ); end
252
298
 
299
+ def self.abstract_subclass?( a_class ) #:nodoc:
300
+ abstract_subclasses.include? a_class
301
+ end
302
+
253
303
  def self.abstract_subclasses #:nodoc:
254
304
  [ MapObject ]
255
305
  end
256
306
 
257
- def self.all; ObjectStore.get_object_store.get_all( self ); end
307
+ # Returns every committed instance of the domain class.
308
+ #
309
+ # User.all # => all users
310
+ def self.all; ObjectStore.get_object_store.all( self ); end
258
311
 
259
- # Returns an array of all fields defined for this class and all concrete
260
- # superclasses.
261
- def self.all_fields
312
+ def self.all_fields # :nodoc:
262
313
  self_and_concrete_superclasses.map { |a_class|
263
314
  a_class.class_fields
264
315
  }.flatten
265
316
  end
266
317
 
267
318
  def self.class_field(fieldName) #:nodoc:
268
- field = nil
269
- self.class_fields.each { |aField|
270
- field = aField if aField.name == fieldName
271
- }
272
- field
319
+ self.class_fields.find { |field| field.name == fieldName }
273
320
  end
274
321
 
275
322
  def self.class_fields #:nodoc:
@@ -286,11 +333,11 @@ module Lafcadio
286
333
  subclass_record.fields
287
334
  end
288
335
 
289
- def self.create_field( field_class, *args )
336
+ def self.create_field( field_class, *args ) #:nodoc:
290
337
  subclass_record.maybe_init_fields
291
338
  att_hash = create_field_att_hash( field_class, *args )
292
339
  field = field_class.create_with_args( self, att_hash )
293
- unless class_fields.any? { |cf| cf.name == field.name }
340
+ unless class_field( field.name )
294
341
  att_hash.each { |field_name, value|
295
342
  setter = field_name + '='
296
343
  field.send( setter, value ) if field.respond_to?( setter )
@@ -302,20 +349,46 @@ module Lafcadio
302
349
  def self.create_field_att_hash( field_class, *args ) #:nodoc:
303
350
  att_hash = args.last
304
351
  unless att_hash.is_a? Hash
305
- att_hash = subclass_record.default_field_setup_hash[
306
- field_class
307
- ].clone
352
+ att_hash = subclass_record.default_field_setup_hash[field_class].clone
308
353
  end
309
354
  if field_class == DomainObjectField
310
355
  att_hash['linked_type'] = args.first
311
356
  att_hash['name'] = args[1] if args[1] and !args[1].is_a? Hash
312
357
  else
313
- name = args.first
314
- att_hash['name'] = name.is_a?( String ) ? name : name.to_s
358
+ att_hash['name'] = args.first.to_s
315
359
  end
316
360
  att_hash
317
361
  end
362
+
363
+ def self.create_fields( field_class, *args ) #:nodoc:
364
+ arg = args.shift
365
+ until args.empty?
366
+ next_arg = args.shift
367
+ if next_arg.is_a? String or next_arg.is_a? Symbol
368
+ create_field( field_class, arg )
369
+ arg = next_arg
370
+ else
371
+ create_field( field_class, arg, next_arg )
372
+ arg = args.shift
373
+ end
374
+ end
375
+ create_field( field_class, arg ) unless arg.nil?
376
+ end
318
377
 
378
+ # Sets a default setup hash for a field of a certain class. Useful for
379
+ # mapping domain classes with lots of fields and unusual field
380
+ # configurations.
381
+ #
382
+ # class LotsOfBooleans < Lafcadio::DomainObject
383
+ # default_field_setup_hash(
384
+ # Lafcadio::BooleanField,
385
+ # {
386
+ # 'enum_type' => Lafcadio::BooleanField::ENUMS_CAPITAL_YES_NO
387
+ # }
388
+ # )
389
+ # booleans 'this', 'that', 'the_other', 'and_another_one',
390
+ # 'this_one_too'
391
+ # end
319
392
  def self.default_field_setup_hash( field_class, hash )
320
393
  subclass_record.default_field_setup_hash[field_class] = hash
321
394
  end
@@ -323,47 +396,101 @@ module Lafcadio
323
396
  def self.dependent_classes #:nodoc:
324
397
  dependent_classes = {}
325
398
  DomainObject.subclasses.each { |aClass|
326
- unless DomainObject.abstract_subclasses.include?( aClass )
327
- if ( field = aClass.get_link_field( self ) )
328
- dependent_classes[aClass] = field
329
- end
399
+ if (
400
+ !abstract_subclass?( aClass ) and
401
+ ( field = aClass.link_field( self ) )
402
+ )
403
+ dependent_classes[aClass] = field
330
404
  end
331
405
  }
332
406
  dependent_classes
333
407
  end
334
408
 
409
+ def self.domain_class #:nodoc:
410
+ self
411
+ end
412
+
413
+ def self.domain_dirs #:nodoc:
414
+ if ( domainDirStr = LafcadioConfig.new['domainDirs'] )
415
+ domainDirStr.split(',')
416
+ else
417
+ []
418
+ end
419
+ end
420
+
421
+ # Tests whether a given domain object exists.
422
+ #
423
+ # User.exist?( 8280 ) # => returns true iff there's a User 8280
424
+ # User.exist?( 'Hwang', :lastName ) # => returns true iff there's a User
425
+ # # with the last name 'Hwang'
335
426
  def self.exist?( search_term, field_name = :pk_id )
336
427
  query = Query.infer( self ) { |dobj|
337
428
  dobj.send( field_name ).equals( search_term )
338
429
  }
339
- !ObjectStore.get_object_store.get_subset( query ).empty?
430
+ !ObjectStore.get_object_store.query( query ).empty?
340
431
  end
341
432
 
433
+ def self.field( fieldName ) #:nodoc:
434
+ aDomainClass = self
435
+ field = nil
436
+ while aDomainClass < DomainObject && !field
437
+ field = aDomainClass.class_field( fieldName )
438
+ aDomainClass = aDomainClass.superclass
439
+ end
440
+ field
441
+ end
442
+
443
+ # Returns the first domain object it can find.
444
+ #
445
+ # very_first_user = User.first
342
446
  def self.first; all.first; end
343
-
447
+
448
+ # This class method has a few uses, depending on how you use it.
449
+ # 1. Pass DomainObject.get a block in order to run a complex query:
450
+ # adult_hwangs = User.get { |user|
451
+ # user.lastName.equals( 'Hwang' ) &
452
+ # user.birthday.lte( Date.today - ( 365 * 18 ) )
453
+ # }
454
+ # See query.rb for more information about how to form these
455
+ # queries.
456
+ # 2. Pass it a simple search term and a field name to retrieve every domain
457
+ # object matching the term. The field name will default to +pk_id+.
458
+ # hwangs = User.get( 'Hwang', :lastName )
459
+ # user123 = User.get( 123 ).first
460
+ # 3. Pass it a { :group => :count } hash to retrieve a count result hash.
461
+ # This interface is fairly new and may be refined (that is, changed) in
462
+ # the future.
463
+ # num_users = User.get( :group => :count ).first[:count]
344
464
  def self.get( *args )
345
465
  if block_given?
346
466
  query = Query.infer( self ) { |dobj| yield( dobj ) }
347
- ObjectStore.get_object_store.get_subset( query )
467
+ ObjectStore.get_object_store.query( query )
348
468
  elsif args.size == 1
349
469
  arg = args.first
350
470
  if arg.is_a? Fixnum
351
471
  ObjectStore.get_object_store.get( self, *args )
352
472
  else
353
473
  qry = Query.new( self, nil, { :group_functions => [ :count ] } )
354
- ObjectStore.get_object_store.query qry
474
+ ObjectStore.get_object_store.group_query qry
355
475
  end
356
476
  else
357
- ObjectStore.get_object_store.get_filtered( self.name, *args )
477
+ search_term = args.shift
478
+ field_name = (
479
+ args.shift or link_field( search_term.domain_class ).name
480
+ )
481
+ qry = Query::Equals.new( field_name, search_term, self )
482
+ ObjectStore.get_object_store.query qry
358
483
  end
359
484
  end
360
485
 
361
486
  # Returns an Array of ObjectField instances for this domain class, parsing
362
- # them from XML if necessary.
487
+ # them from XML if necessary. You can override this to define a domain
488
+ # class' fields, though it will probably just be simpler to use one-line
489
+ # class methods instead.
363
490
  def self.get_class_fields
364
491
  if self.methods( false ).include?( 'get_class_fields' )
365
492
  [ subclass_record.pk_field ]
366
- elsif abstract_subclasses.include?( self )
493
+ elsif abstract_subclass?( self )
367
494
  []
368
495
  else
369
496
  xmlParser = try_load_xml_parser
@@ -377,107 +504,64 @@ module Lafcadio
377
504
  end
378
505
  end
379
506
 
380
- def self.get_domain_dirs #:nodoc:
381
- if ( domainDirStr = LafcadioConfig.new['domainDirs'] )
382
- domainDirStr.split(',')
383
- else
384
- []
385
- end
386
- end
387
-
388
- def self.get_field( fieldName ) #:nodoc:
389
- aDomainClass = self
390
- field = nil
391
- while aDomainClass < DomainObject && !field
392
- field = aDomainClass.class_field( fieldName )
393
- aDomainClass = aDomainClass.superclass
394
- end
395
- if field
396
- field
397
- else
398
- errStr = "Couldn't find field \"#{ fieldName }\" in " +
399
- "#{ self } domain class"
400
- raise( MissingError, errStr, caller )
401
- end
507
+ def self.is_child_domain_class? #:nodoc:
508
+ superclass != DomainObject and !abstract_subclass?( superclass )
402
509
  end
403
510
 
404
- def self.get_domain_class_from_string(typeString) #:nodoc:
405
- domain_class = nil
406
- require_domain_file( typeString )
407
- subclasses.each { |subclass|
408
- domain_class = subclass if subclass.to_s == typeString
409
- }
410
- if domain_class
411
- domain_class
412
- else
413
- raise CouldntMatchDomainClassError,
414
- "couldn't match domain_class #{typeString}", caller
415
- end
416
- end
511
+ # Returns the last domain object.
512
+ #
513
+ # last_msg = Message.last
514
+ def self.last; all.last; end
417
515
 
418
- def self.get_link_field( linked_domain_class ) # :nodoc:
516
+ def self.link_field( linked_domain_class ) # :nodoc:
419
517
  class_fields.find { |field|
420
- field.is_a? DomainObjectField and field.linked_type == linked_domain_class
518
+ field.is_a? DomainObjectField and
519
+ field.linked_type == linked_domain_class
421
520
  }
422
521
  end
423
522
 
424
- def self.inherited(subclass) #:nodoc:
425
- @@subclass_records[subclass]
426
- end
427
-
428
- def self.is_based_on? #:nodoc:
429
- self.superclass.is_concrete?
430
- end
431
-
432
- def self.is_concrete? #:nodoc:
433
- (self != DomainObject && abstract_subclasses.index(self).nil?)
434
- end
435
-
436
- def self.last; all.last; end
437
-
438
523
  def self.method_missing( methodId, *args ) #:nodoc:
524
+ dispatched = false
439
525
  method_name = methodId.id2name
440
- maybe_field_class_name = method_name.underscore_to_camel_case + 'Field'
441
526
  begin
442
- field_class = Lafcadio.const_get( maybe_field_class_name )
443
- create_field( field_class, *args )
527
+ method_missing_try_create_field( method_name, *args )
528
+ dispatched = true
444
529
  rescue NameError
445
- singular = method_name.singular
446
- if singular
447
- maybe_field_class_name = singular.underscore_to_camel_case + 'Field'
448
- begin
449
- field_class = Lafcadio.const_get( maybe_field_class_name )
450
- arg = args.shift
451
- until args.empty?
452
- next_arg = args.shift
453
- if next_arg.is_a? String or next_arg.is_a? Symbol
454
- create_field( field_class, arg )
455
- arg = next_arg
456
- else
457
- create_field( field_class, arg, next_arg )
458
- arg = args.shift
459
- end
460
- end
461
- create_field( field_class, arg ) unless arg.nil?
462
- rescue NameError
463
- super
464
- end
465
- else
466
- super
467
- end
530
+ begin
531
+ method_missing_try_create_fields( method_name, *args )
532
+ dispatched = true
533
+ rescue NameError; end
468
534
  end
535
+ super unless dispatched
469
536
  end
470
537
 
471
- def self.only; all.only; end
472
-
473
- def self.domain_class #:nodoc:
474
- self
538
+ def self.method_missing_try_create_field( method_name, *args ) # :nodoc:
539
+ maybe_field_class_name = method_name.underscore_to_camel_case + 'Field'
540
+ field_class = Lafcadio.const_get( maybe_field_class_name )
541
+ create_field( field_class, *args )
542
+ end
543
+
544
+ def self.method_missing_try_create_fields( method_name, *args ) # :nodoc:
545
+ singular = method_name.singular
546
+ if singular
547
+ maybe_field_class_name = singular.underscore_to_camel_case + 'Field'
548
+ field_class = Lafcadio.const_get( maybe_field_class_name )
549
+ create_fields( field_class, *args )
550
+ else
551
+ raise NameError
552
+ end
475
553
  end
476
554
 
477
- def self.require_domain_file( typeString )
555
+ # Returns the only committed instance of the domain class. Will raise an
556
+ # IndexError if there are 0, or more than 1, domain objects.
557
+ #
558
+ # the_one_user = User.only
559
+ def self.only; all.only; end
560
+
561
+ def self.require_domain_file( typeString ) # :nodoc:
478
562
  typeString =~ /([^\:]*)$/
479
563
  fileName = $1
480
- get_domain_dirs.each { |domainDir|
564
+ domain_dirs.each { |domainDir|
481
565
  if Dir.entries(domainDir).index("#{fileName}.rb")
482
566
  require "#{ domainDir }#{ fileName }"
483
567
  end
@@ -491,43 +575,66 @@ module Lafcadio
491
575
  def self.self_and_concrete_superclasses # :nodoc:
492
576
  classes = [ ]
493
577
  a_domain_class = self
494
- until( a_domain_class == DomainObject ||
495
- abstract_subclasses.index( a_domain_class ) != nil )
578
+ until(
579
+ a_domain_class == DomainObject || abstract_subclass?( a_domain_class )
580
+ )
496
581
  classes << a_domain_class
497
582
  a_domain_class = a_domain_class.superclass
498
583
  end
499
584
  classes
500
585
  end
501
586
 
502
- def self.singleton_method_added( symbol )
587
+ def self.singleton_method_added( symbol ) # :nodoc:
503
588
  if symbol.id2name == 'sql_primary_key_name' && self < DomainObject
504
589
  begin
505
- get_field( 'pk_id' ).db_field_name = self.send( symbol )
590
+ field( 'pk_id' ).db_field_name = self.send( symbol )
506
591
  rescue NameError
507
592
  subclass_record.sql_primary_key = self.send( symbol )
508
593
  end
509
594
  end
510
595
  end
511
596
 
512
- # Returns the name of the primary key in the database, retrieving it from
513
- # the class definition XML if necessary.
514
- def self.sql_primary_key_name( set_sql_primary_key_name = nil )
515
- if set_sql_primary_key_name
516
- get_field( 'pk_id' ).db_field_name = set_sql_primary_key_name
517
- end
518
- get_field( 'pk_id' ).db_field_name
597
+ # If +set_db_field_name+ is nil, this will return the sql name of the
598
+ # primary key. If +set_db_field_name+ isn't nil, it will set the sql name.
599
+ #
600
+ # class User < Lafcadio::DomainObject
601
+ # string 'firstNames'
602
+ # end
603
+ # User.sql_primary_key_name # => 'pk_id'
604
+ # class User < Lafcadio::DomainObject
605
+ # sql_primary_key_name 'some_other_id'
606
+ # end
607
+ # User.sql_primary_key_name # => 'some_other_id'
608
+ def self.sql_primary_key_name( set_db_field_name = nil )
609
+ field( 'pk_id' ).db_field_name = set_db_field_name if set_db_field_name
610
+ field( 'pk_id' ).db_field_name
519
611
  end
520
612
 
521
- def self.subclass_record; @@subclass_records[self]; end
613
+ def self.subclass_record # :nodoc:
614
+ @@subclass_records[self]
615
+ end
522
616
 
523
617
  def self.subclasses #:nodoc:
524
618
  @@subclass_records.keys
525
619
  end
526
620
 
527
- # Returns the table name, which is assumed to be the domain class name
528
- # pluralized, and with the first letter lowercase. A User class is
529
- # assumed to be stored in a "users" table, while a ProductCategory class is
530
- # assumed to be stored in a "productCategories" table.
621
+ # If +set_table_name+ is nil, DomainObject.table_name will return the table
622
+ # name. Lafcadio assumes that a domain class corresponds to a table whose
623
+ # name is the pluralized, lower-case, underscored version of the class
624
+ # name. A User class is assumed to be stored in a "users" table, while a
625
+ # ProductCategory class is assumed to be stored in a "product_categories"
626
+ # table.
627
+ #
628
+ # If +set_table_name+ is not nil, this will set the table name.
629
+ #
630
+ # class User < Lafcadio::DomainObject
631
+ # string 'firstNames'
632
+ # end
633
+ # User.table_name # => 'users'
634
+ # class User < Lafcadio::DomainObject
635
+ # table_name 'some_table'
636
+ # end
637
+ # User.table_name # => 'some_table'
531
638
  def self.table_name( set_table_name = nil )
532
639
  if set_table_name
533
640
  @table_name = set_table_name
@@ -543,30 +650,31 @@ module Lafcadio
543
650
  end
544
651
  end
545
652
 
546
- def self.try_load_xml_parser
653
+ def self.try_load_xml_parser # :nodoc:
547
654
  require 'lafcadio/domain'
548
655
  dirName = LafcadioConfig.new['classDefinitionDir']
549
656
  xmlFileName = self.basename + '.xml'
550
657
  xmlPath = File.join( dirName, xmlFileName )
551
- xml = ''
552
658
  begin
553
- File.open( xmlPath ) { |file| xml = file.readlines.join }
659
+ xml = File.open( xmlPath ) do |f| f.gets( nil ); end
554
660
  ClassDefinitionXmlParser.new( self, xml )
555
661
  rescue Errno::ENOENT
556
662
  # no xml file, so no @xmlParser
557
663
  end
558
664
  end
559
665
 
560
- attr_accessor :error_messages, :last_commit, :fields, :fields_set
666
+ attr_accessor :fields_set, :field_values, :last_commit_type
561
667
  attr_reader :delete
562
- protected :fields, :fields_set
668
+ protected :fields_set, :field_values
563
669
 
564
- # fieldHash should contain key-value associations for the different
670
+ # +field_hash+ should contain key-value associations for the different
565
671
  # fields of this domain class. For example, instantiating a User class
566
672
  # might look like:
567
673
  #
568
- # User.new( 'firstNames' => 'John', 'lastName' => 'Doe',
569
- # 'email' => 'john.doe@email.com', 'password' => 'l33t' )
674
+ # User.new(
675
+ # 'firstNames' => 'John', 'lastName' => 'Doe',
676
+ # 'email' => 'john.doe@email.com', 'password' => 'l33t'
677
+ # )
570
678
  #
571
679
  # In normal usage any code you write that creates a domain object will not
572
680
  # define the +pk_id+ field. The system assumes that a domain object with an
@@ -575,27 +683,11 @@ module Lafcadio
575
683
  #
576
684
  # If you're creating mock objects for unit tests, you can explicitly set
577
685
  # the +pk_id+ to represent objects that already exist in the database.
578
- def initialize(fieldHash)
579
- if fieldHash.respond_to? :keys
580
- fieldHash.keys.each { |key|
581
- key = key.to_s
582
- begin
583
- self.class.get_field( key )
584
- rescue MissingError
585
- raise ArgumentError, "Invalid field name #{ key }"
586
- end
587
- }
588
- end
589
- if fieldHash.is_a? Hash
590
- @fieldHash = {}
591
- fieldHash.each do |k, v| @fieldHash[k.to_s] = v; end
592
- else
593
- @fieldHash = fieldHash
594
- end
595
- @error_messages = []
596
- @fields = {}
686
+ def initialize( field_hash )
687
+ field_hash = preprocess_field_hash field_hash
688
+ @field_values = {}
597
689
  @fields_set = []
598
- @original_values = OriginalValuesHash.new( @fieldHash )
690
+ reset_original_values_hash @fieldHash
599
691
  check_fields = LafcadioConfig.new()['checkFields']
600
692
  verify if %w( onInstantiate onAllStates ).include?( check_fields )
601
693
  end
@@ -603,7 +695,7 @@ module Lafcadio
603
695
  # Returns a clone, with all of the fields copied.
604
696
  def clone
605
697
  copy = super
606
- copy.fields = @fields.clone
698
+ copy.field_values = @field_values.clone
607
699
  copy.fields_set = @fields_set.clone
608
700
  copy
609
701
  end
@@ -622,106 +714,125 @@ module Lafcadio
622
714
  @delete = value
623
715
  end
624
716
 
717
+ # Deletes a domain object, committing the delete to the database
718
+ # immediately.
625
719
  def delete!
626
720
  self.delete = true
627
721
  commit
628
722
  end
629
723
 
630
- def get_field( field ) #:nodoc:
724
+ # Returns the subclass of DomainObject that this instance represents.
725
+ # Because of the way that proxying works, clients should call this method
726
+ # instead of Object.class.
727
+ def domain_class
728
+ self.class.domain_class
729
+ end
730
+
731
+ def field_value( field ) #:nodoc:
631
732
  unless @fields_set.include?( field )
632
- set_field( field, @fieldHash[field.name] )
733
+ set_field_value( field, @fieldHash[field.name] )
633
734
  end
634
- @fields[field.name]
735
+ @field_values[field.name]
635
736
  end
636
737
 
637
- def get_getter_field( methId ) #:nodoc:
638
- begin
639
- self.class.get_field( methId.id2name )
640
- rescue MissingError
641
- nil
642
- end
738
+ def getter_field( methId ) #:nodoc:
739
+ self.class.field methId.id2name
643
740
  end
644
741
 
645
- def get_setter_field( methId ) #:nodoc:
646
- if methId.id2name =~ /(.*)=$/
647
- begin
648
- self.class.get_field( $1 )
649
- rescue MissingError
650
- nil
651
- end
652
- else
653
- nil
654
- end
655
- end
656
-
657
742
  def method_missing( methId, *args ) #:nodoc:
658
- if ( field = get_setter_field( methId ) )
659
- set_field( field, args.first )
660
- elsif ( field = get_getter_field( methId ) )
661
- get_field( field )
743
+ if ( field = setter_field( methId ) )
744
+ set_field_value( field, args.first )
745
+ elsif ( field = getter_field( methId ) )
746
+ field_value( field )
662
747
  else
663
- new_symbol = ( 'get_' + methId.id2name ).to_sym
664
748
  object_store = ObjectStore.get_object_store
665
- if object_store.respond_to? new_symbol
749
+ if object_store.respond_to? methId
666
750
  args = [ self ].concat args
667
- object_store.send( 'get_' + methId.id2name, *args )
751
+ object_store.send( methId, *args )
668
752
  else
669
753
  super( methId, *args )
670
754
  end
671
755
  end
672
756
  end
673
757
 
674
- # Returns the subclass of DomainObject that this instance represents.
675
- # Because of the way that proxying works, clients should call this method
676
- # instead of Object.class.
677
- def domain_class
678
- self.class.domain_class
679
- end
680
-
681
758
  # This template method is called before every commit. Subclasses can
682
759
  # override it to ensure code is executed before a commit.
683
760
  def pre_commit_trigger
684
761
  nil
685
762
  end
686
763
 
764
+ def preprocess_field_hash( fieldHash ) # :nodoc:
765
+ if fieldHash.is_a? Hash
766
+ fieldHash.keys.each { |key|
767
+ if self.class.field( key.to_s ).nil?
768
+ raise ArgumentError, "Invalid field name #{ key.to_s }"
769
+ end
770
+ }
771
+ @fieldHash = {}
772
+ fieldHash.each do |k, v| @fieldHash[k.to_s] = v; end
773
+ else
774
+ @fieldHash = fieldHash
775
+ end
776
+ end
777
+
687
778
  # This template method is called after every commit. Subclasses can
688
779
  # override it to ensure code is executed after a commit.
689
780
  def post_commit_trigger
690
781
  nil
691
782
  end
692
783
 
693
- def set_field( field, value ) #:nodoc:
694
- if field.class <= DomainObjectField
695
- if value.class != DomainObjectProxy && value
696
- value = DomainObjectProxy.new(value)
697
- end
784
+ def reset_original_values_hash( f = @field_values ) #:nodoc:
785
+ @original_values = ReadOnlyHash.new( f.clone )
786
+ end
787
+
788
+ def set_field_value( field, value ) #:nodoc:
789
+ if (
790
+ field.is_a?( DomainObjectField ) and
791
+ !value.is_a?( DomainObjectProxy ) and value
792
+ )
793
+ value = DomainObjectProxy.new(value)
698
794
  end
699
795
  if ( LafcadioConfig.new()['checkFields'] == 'onAllStates' &&
700
796
  !field.instance_of?( PrimaryKeyField ) )
701
797
  field.verify( value, pk_id )
702
798
  end
703
- @fields[field.name] = value
799
+ @field_values[field.name] = value
704
800
  @fields_set << field
705
801
  end
706
802
 
803
+ def setter_field( methId ) #:nodoc:
804
+ if methId.id2name =~ /(.*)=$/
805
+ self.class.field $1
806
+ else
807
+ nil
808
+ end
809
+ end
810
+
811
+ # Updates a domain object and commits the changes to the database
812
+ # immediately.
813
+ #
814
+ # user99 = User[99]
815
+ # user99.update!( :firstNames => 'Bill', :password => 'n3wp4ssw0rd' )
707
816
  def update!( changes )
708
817
  changes.each do |sym, value| self.send( sym.to_s + '=', value ); end
709
818
  commit
710
819
  end
711
820
 
821
+ # If you're running against a MockObjectStore, this will verify each field
822
+ # and raise an error if there's any invalid fields.
712
823
  def verify
713
- if ObjectStore.get_object_store.mock?
824
+ if ObjectStore.mock?
714
825
  self.class.get_class_fields.each { |field|
715
826
  field.verify( self.send( field.name ), self.pk_id )
716
827
  }
717
828
  end
718
829
  end
719
830
 
720
- class OriginalValuesHash < DelegateClass( Hash )
831
+ class ReadOnlyHash < DelegateClass( Hash ) # :nodoc:
721
832
  def []=( key, val ); raise NoMethodError; end
722
833
  end
723
834
 
724
- class SubclassRecord
835
+ class SubclassRecord # :nodoc:
725
836
  attr_accessor :default_field_setup_hash, :fields, :sql_primary_key
726
837
 
727
838
  def initialize( subclass )
@@ -746,16 +857,13 @@ module Lafcadio
746
857
 
747
858
  # Any domain class that is used mostly to map between two other domain
748
859
  # classes should be a subclass of MapObject. Subclasses of MapObject should
749
- # override MapObject.mappedTypes, returning a two-element array containing
860
+ # override MapObject.mapped_classes, returning a two-element array containing
750
861
  # the domain classes that the map object maps between.
751
862
  class MapObject < DomainObject
752
863
  def self.other_mapped_type(firstType) #:nodoc:
753
- types = mappedTypes
754
- if types.index(firstType) == 0
755
- types[1]
756
- else
757
- types[0]
758
- end
864
+ mt = mapped_classes.clone
865
+ mt.delete firstType
866
+ mt.only
759
867
  end
760
868
 
761
869
  def self.subsidiary_map #:nodoc: