lafcadio 0.9.0 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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:
|