lafcadio 0.9.0 → 0.9.1
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/lafcadio.rb +1 -1
- data/lib/lafcadio.rb~ +1 -1
- data/lib/lafcadio/domain.rb +363 -255
- data/lib/lafcadio/domain.rb~ +201 -230
- data/lib/lafcadio/mock.rb +64 -70
- data/lib/lafcadio/mock.rb~ +48 -49
- data/lib/lafcadio/objectField.rb +137 -127
- data/lib/lafcadio/objectField.rb~ +7 -7
- data/lib/lafcadio/objectStore.rb +560 -586
- data/lib/lafcadio/objectStore.rb~ +495 -536
- data/lib/lafcadio/query.rb +320 -213
- data/lib/lafcadio/query.rb~ +129 -131
- data/lib/lafcadio/schema.rb +10 -14
- data/lib/lafcadio/schema.rb~ +1 -1
- data/lib/lafcadio/test.rb +174 -75
- data/lib/lafcadio/test.rb~ +191 -0
- data/lib/lafcadio/util.rb +1 -32
- data/lib/lafcadio/util.rb~ +1 -6
- metadata +2 -2
data/lib/lafcadio.rb
CHANGED
data/lib/lafcadio.rb~
CHANGED
data/lib/lafcadio/domain.rb
CHANGED
@@ -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.
|
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( '
|
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
|
150
|
-
# 1.
|
151
|
-
#
|
152
|
-
#
|
153
|
-
#
|
154
|
-
#
|
155
|
-
#
|
156
|
-
#
|
157
|
-
#
|
158
|
-
#
|
159
|
-
#
|
160
|
-
#
|
161
|
-
#
|
162
|
-
#
|
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
|
-
#
|
180
|
-
#
|
181
|
-
#
|
182
|
-
#
|
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
|
190
|
-
# relation.
|
191
|
-
#
|
192
|
-
#
|
193
|
-
#
|
194
|
-
#
|
195
|
-
#
|
196
|
-
#
|
197
|
-
#
|
198
|
-
|
199
|
-
# msg = Message.new(
|
200
|
-
|
201
|
-
|
202
|
-
#
|
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
|
206
|
-
# this key is named +pk_id+ in the database, though that can be
|
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
|
209
|
-
# +pk_id+ to the new instance. The pk_id will automatically be set
|
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,
|
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,
|
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
|
223
|
-
# class is assumed to be stored in a "users" table, while a ProductCategory
|
224
|
-
# class is assumed to be stored in a "
|
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
|
-
|
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
|
-
|
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
|
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
|
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
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
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.
|
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.
|
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.
|
474
|
+
ObjectStore.get_object_store.group_query qry
|
355
475
|
end
|
356
476
|
else
|
357
|
-
|
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
|
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.
|
381
|
-
|
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
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
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.
|
516
|
+
def self.link_field( linked_domain_class ) # :nodoc:
|
419
517
|
class_fields.find { |field|
|
420
|
-
field.is_a? DomainObjectField and
|
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
|
-
|
443
|
-
|
527
|
+
method_missing_try_create_field( method_name, *args )
|
528
|
+
dispatched = true
|
444
529
|
rescue NameError
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
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.
|
472
|
-
|
473
|
-
|
474
|
-
|
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
|
-
|
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
|
-
|
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(
|
495
|
-
|
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
|
-
|
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
|
-
#
|
513
|
-
#
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
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
|
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
|
-
#
|
528
|
-
#
|
529
|
-
#
|
530
|
-
# assumed to be stored in a "
|
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 )
|
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 :
|
666
|
+
attr_accessor :fields_set, :field_values, :last_commit_type
|
561
667
|
attr_reader :delete
|
562
|
-
protected :
|
668
|
+
protected :fields_set, :field_values
|
563
669
|
|
564
|
-
#
|
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(
|
569
|
-
#
|
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(
|
579
|
-
|
580
|
-
|
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
|
-
|
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.
|
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
|
-
|
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
|
-
|
733
|
+
set_field_value( field, @fieldHash[field.name] )
|
633
734
|
end
|
634
|
-
@
|
735
|
+
@field_values[field.name]
|
635
736
|
end
|
636
737
|
|
637
|
-
def
|
638
|
-
|
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 =
|
659
|
-
|
660
|
-
elsif ( field =
|
661
|
-
|
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?
|
749
|
+
if object_store.respond_to? methId
|
666
750
|
args = [ self ].concat args
|
667
|
-
object_store.send(
|
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
|
694
|
-
|
695
|
-
|
696
|
-
|
697
|
-
|
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
|
-
@
|
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.
|
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
|
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.
|
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
|
-
|
754
|
-
|
755
|
-
|
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:
|