lafcadio 0.6.0 → 0.6.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 CHANGED
@@ -16,7 +16,7 @@
16
16
  # http://lafcadio.rubyforge.org/tutorial.html.
17
17
 
18
18
  module Lafcadio
19
- Version = "0.6.0"
19
+ Version = "0.6.1"
20
20
 
21
21
  require 'lafcadio/dateTime'
22
22
  require 'lafcadio/depend'
@@ -26,6 +26,5 @@ module Lafcadio
26
26
  require 'lafcadio/objectStore'
27
27
  require 'lafcadio/query'
28
28
  require 'lafcadio/schema'
29
- require 'lafcadio/test'
30
29
  require 'lafcadio/util'
31
30
  end
data/lib/lafcadio.rb~ CHANGED
@@ -16,7 +16,7 @@
16
16
  # http://lafcadio.rubyforge.org/tutorial.html.
17
17
 
18
18
  module Lafcadio
19
- Version = "0.5.2"
19
+ Version = "0.6.0"
20
20
 
21
21
  require 'lafcadio/dateTime'
22
22
  require 'lafcadio/depend'
@@ -26,6 +26,5 @@ module Lafcadio
26
26
  require 'lafcadio/objectStore'
27
27
  require 'lafcadio/query'
28
28
  require 'lafcadio/schema'
29
- require 'lafcadio/test'
30
29
  require 'lafcadio/util'
31
30
  end
metadata CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.8.1
3
3
  specification_version: 1
4
4
  name: lafcadio
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.6.0
7
- date: 2005-01-02
6
+ version: 0.6.1
7
+ date: 2005-01-20
8
8
  summary: Lafcadio is an object-relational mapping layer
9
9
  require_paths:
10
10
  - lib
@@ -36,7 +36,6 @@ files:
36
36
  - lib/lafcadio/dateTime.rb
37
37
  - lib/lafcadio/depend.rb
38
38
  - lib/lafcadio/domain.rb
39
- - lib/lafcadio/domain.rb~
40
39
  - lib/lafcadio/mock.rb
41
40
  - lib/lafcadio/objectField.rb
42
41
  - lib/lafcadio/objectStore.rb
@@ -45,9 +44,7 @@ files:
45
44
  - lib/lafcadio/test
46
45
  - lib/lafcadio/test.rb
47
46
  - lib/lafcadio/util.rb
48
- - lib/lafcadio/util.rb~
49
47
  - lib/lafcadio/test/testconfig.dat
50
- - lib/lafcadio/test/testconfig.dat~
51
48
  test_files: []
52
49
  rdoc_options: []
53
50
  extra_rdoc_files: []
@@ -1,641 +0,0 @@
1
- require 'lafcadio/objectField'
2
- require 'lafcadio/util'
3
- require 'rexml/document'
4
-
5
- module Lafcadio
6
- class ClassDefinitionXmlParser # :nodoc: all
7
- def initialize( domain_class, xml )
8
- @domain_class = domain_class
9
- @xmlDocRoot = REXML::Document.new( xml ).root
10
- @namesProcessed = {}
11
- end
12
-
13
- def get_class_field( fieldElt )
14
- className = fieldElt.attributes['class'].to_s
15
- name = fieldElt.attributes['name']
16
- if className != ''
17
- fieldClass = Class.by_name( 'Lafcadio::' + className )
18
- register_name( name )
19
- field = fieldClass.instantiate_from_xml( @domain_class, fieldElt )
20
- set_field_attributes( field, fieldElt )
21
- else
22
- msg = "Couldn't find field class '#{ className }' for field " +
23
- "'#{ name }'"
24
- raise( MissingError, msg, caller )
25
- end
26
- field
27
- end
28
-
29
- def get_class_fields
30
- namesProcessed = {}
31
- pk_field = PrimaryKeyField.new( @domain_class )
32
- if ( spkn = @xmlDocRoot.attributes['sql_primary_key_name'] )
33
- pk_field.db_field_name = spkn
34
- end
35
- fields = [ pk_field ]
36
- @xmlDocRoot.elements.each('field') { |fieldElt|
37
- fields << get_class_field( fieldElt )
38
- }
39
- fields
40
- end
41
-
42
- def possible_field_attributes
43
- fieldAttr = []
44
- fieldAttr << FieldAttribute.new( 'size', FieldAttribute::INTEGER )
45
- fieldAttr << FieldAttribute.new( 'unique', FieldAttribute::BOOLEAN )
46
- fieldAttr << FieldAttribute.new( 'not_null', FieldAttribute::BOOLEAN )
47
- fieldAttr << FieldAttribute.new( 'enum_type', FieldAttribute::ENUM,
48
- BooleanField )
49
- fieldAttr << FieldAttribute.new( 'enums', FieldAttribute::HASH )
50
- fieldAttr << FieldAttribute.new( 'range', FieldAttribute::ENUM,
51
- DateField )
52
- fieldAttr << FieldAttribute.new( 'large', FieldAttribute::BOOLEAN )
53
- end
54
-
55
- def register_name( name )
56
- raise InvalidDataError if @namesProcessed[name]
57
- @namesProcessed[name] = true
58
- end
59
-
60
- def set_field_attributes( field, fieldElt )
61
- possible_field_attributes.each { |fieldAttr|
62
- fieldAttr.maybe_set_field_attr( field, fieldElt )
63
- }
64
- end
65
-
66
- def table_name
67
- @xmlDocRoot.attributes['table_name']
68
- end
69
-
70
- class FieldAttribute
71
- INTEGER = 1
72
- BOOLEAN = 2
73
- ENUM = 3
74
- HASH = 4
75
-
76
- attr_reader :name, :value_class
77
-
78
- def initialize( name, value_class, objectFieldClass = nil )
79
- @name = name; @value_class = value_class
80
- @objectFieldClass = objectFieldClass
81
- end
82
-
83
- def maybe_set_field_attr( field, fieldElt )
84
- setterMethod = "#{ name }="
85
- if field.respond_to?( setterMethod )
86
- if value_class != FieldAttribute::HASH
87
- if ( attrStr = fieldElt.attributes[name] )
88
- field.send( setterMethod, value_from_string( attrStr ) )
89
- end
90
- else
91
- if ( attrElt = fieldElt.elements[name] )
92
- field.send( setterMethod, value_from_elt( attrElt ) )
93
- end
94
- end
95
- end
96
- end
97
-
98
- def value_from_elt( elt )
99
- hash = {}
100
- elt.elements.each( English.singular( @name ) ) { |subElt|
101
- key = subElt.attributes['key'] == 'true'
102
- value = subElt.text.to_s
103
- hash[key] = value
104
- }
105
- hash
106
- end
107
-
108
- def value_from_string( valueStr )
109
- if @value_class == INTEGER
110
- valueStr.to_i
111
- elsif @value_class == BOOLEAN
112
- valueStr == 'y'
113
- elsif @value_class == ENUM
114
- eval "#{ @objectFieldClass.name }::#{ valueStr }"
115
- end
116
- end
117
- end
118
-
119
- class InvalidDataError < ArgumentError; end
120
- end
121
-
122
- module DomainComparable
123
- include Comparable
124
-
125
- # A DomainObject or DomainObjectProxy is compared by +domain_class+ and by
126
- # +pk_id+.
127
- def <=>(anOther)
128
- if anOther.respond_to?( 'domain_class' )
129
- if self.domain_class == anOther.domain_class
130
- self.pk_id <=> anOther.pk_id
131
- else
132
- self.domain_class.name <=> anOther.domain_class.name
133
- end
134
- else
135
- nil
136
- end
137
- end
138
-
139
- def eql?(otherObj)
140
- self == otherObj
141
- end
142
-
143
- def hash; "#{ self.class.name } #{ pk_id }".hash; end
144
- end
145
-
146
- # All classes that correspond to a table in the database need to be children
147
- # of DomainObject.
148
- #
149
- # = Defining fields
150
- # There are two ways to define the fields of a DomainObject subclass.
151
- # 1. Defining fields in an XML file. To do this,
152
- # 1. Set one directory to contain all your XML files, by setting
153
- # +classDefinitionDir+ in your LafcadioConfig file.
154
- # 2. Write one XML file per domain class. For example, a User.xml file
155
- # might look like:
156
- # <lafcadio_class_definition name="User">
157
- # <field name="lastName" class="TextField"/>
158
- # <field name="email" class="TextField"/>
159
- # <field name="password" class="TextField"/>
160
- # <field name="birthday" class="DateField"/>
161
- # </lafcadio_class_definition>
162
- # 2. Overriding DomainObject.get_class_fields. The method should return an Array
163
- # of instances of ObjectField or its children. The order is unimportant.
164
- # For example:
165
- # class User < DomainObject
166
- # def User.get_class_fields
167
- # fields = []
168
- # fields << TextField.new(self, 'firstName')
169
- # fields << TextField.new(self, 'lastName')
170
- # fields << TextField.new(self, 'email')
171
- # fields << TextField.new(self, 'password')
172
- # fields << DateField.new(self, 'birthday')
173
- # fields
174
- # end
175
- # end
176
- #
177
- # = Setting and retrieving fields
178
- # Once your fields are defined, you can create an instance by passing in a
179
- # hash of field names and values.
180
- # john = User.new( 'firstName' => 'John', 'lastName' => 'Doe',
181
- # 'email' => 'john.doe@email.com',
182
- # 'password' => 'my_password',
183
- # 'birthday' => tenYearsAgo )
184
- #
185
- # You can read and write these fields like normal instance attributes.
186
- # john.email => 'john.doe@email.com'
187
- # john.email = 'john.doe@mail.email.com'
188
- #
189
- # If your domain class has fields that refer to other domain classes, or even
190
- # to another row in the same table, you can use a LinkField to express the
191
- # relation.
192
- # <lafcadio_class_definition name="Message">
193
- # <field name="subject" class="TextField" />
194
- # <field name="body" class="TextField" />
195
- # <field name="author" class="LinkField" linked_type="User" />
196
- # <field name="recipient" class="LinkField" linked_type="User" />
197
- # <field name="dateSent" class="DateField" />
198
- # </lafcadio_class_definition>
199
- #
200
- # msg = Message.new( 'subject' => 'hi there',
201
- # 'body' => 'You wanna go to the movies on Saturday?',
202
- # 'author' => john, 'recipient' => jane,
203
- # 'dateSent' => Date.today )
204
- #
205
- # = pk_id and committing
206
- # Lafcadio requires that each table has a numeric primary key. It assumes that
207
- # this key is named +pk_id+ in the database, though that can be overridden.
208
- #
209
- # When you create a domain object by calling new, you should not assign a
210
- # +pk_id+ to the new instance. The pk_id will automatically be set when you
211
- # commit the object by calling DomainObject#commit.
212
- #
213
- # However, you may want to manually set +pk_id+ when setting up a test case, so
214
- # you can ensure that a domain object has a given primary key.
215
- #
216
- # = Naming assumptions, and how to override them
217
- # By default, Lafcadio assumes that every domain object is indexed by the
218
- # field +pk_id+ in the database schema. If you're dealing with a table that
219
- # uses a different field name, override DomainObject.sql_primary_key_name.
220
- # However, you will always use +pk_id+ in your Ruby code.
221
- #
222
- # Lafcadio assumes that a domain class corresponds to a table whose name is
223
- # the plural of the class name, and whose first letter is lowercase. A User
224
- # class is assumed to be stored in a "users" table, while a ProductCategory
225
- # class is assumed to be stored in a "productCategories" table. Override
226
- # DomainObject.table_name to override this behavior.
227
- #
228
- # = Inheritance
229
- # Domain classes can inherit from other domain classes; they have all the
230
- # fields of any concrete superclasses plus any new fields defined for
231
- # themselves. You can use normal inheritance to define this:
232
- # class User < DomainObject
233
- # ...
234
- # end
235
- #
236
- # class Administrator < User
237
- # ...
238
- # end
239
- #
240
- # Lafcadio assumes that each concrete class has a corresponding table, and
241
- # that each table has a +pk_id+ field that is used to match rows between
242
- # different levels.
243
- class DomainObject
244
- @@subclassHash = {}
245
- @@class_fields = {}
246
- @@pk_fields =
247
- Hash.new { |hash, key|
248
- pk_field = PrimaryKeyField.new( key )
249
- pk_field.db_field_name = @@sql_primary_keys[key]
250
- hash[key] = pk_field
251
- }
252
- @@sql_primary_keys = Hash.new( 'pk_id' )
253
-
254
- COMMIT_ADD = 1
255
- COMMIT_EDIT = 2
256
- COMMIT_DELETE = 3
257
-
258
- include DomainComparable
259
-
260
- def self.abstract_subclasses #:nodoc:
261
- require 'lafcadio/domain'
262
- [ MapObject ]
263
- end
264
-
265
- # Returns an array of all fields defined for this class and all concrete
266
- # superclasses.
267
- def self.all_fields
268
- all_fields = []
269
- self_and_concrete_superclasses.each { |aClass|
270
- aClass.class_fields.each { |field| all_fields << field }
271
- }
272
- all_fields
273
- end
274
-
275
- def self.class_fields #:nodoc:
276
- class_fields = @@class_fields[self]
277
- unless class_fields
278
- @@class_fields[self] = self.get_class_fields
279
- class_fields = @@class_fields[self]
280
- end
281
- class_fields
282
- end
283
-
284
- def self.create_field( field_class, name, att_hash )
285
- class_fields = @@class_fields[self]
286
- if class_fields.nil?
287
- class_fields = [ @@pk_fields[self] ]
288
- @@class_fields[self] = class_fields
289
- end
290
- att_hash['name'] = name
291
- field = field_class.instantiate_with_parameters( self, att_hash )
292
- att_hash.each { |field_name, value|
293
- setter = field_name + '='
294
- field.send( setter, value ) if field.respond_to?( setter )
295
- }
296
- class_fields << field
297
- end
298
-
299
- def self.dependent_classes #:nodoc:
300
- dependent_classes = {}
301
- DomainObject.subclasses.each { |aClass|
302
- if aClass != DomainObjectProxy &&
303
- (!DomainObject.abstract_subclasses.index(aClass))
304
- aClass.class_fields.each { |field|
305
- if ( field.is_a?( LinkField ) &&
306
- field.linked_type == self.domain_class )
307
- dependent_classes[aClass] = field
308
- end
309
- }
310
- end
311
- }
312
- dependent_classes
313
- end
314
-
315
- def self.get_class_field(fieldName) #:nodoc:
316
- field = nil
317
- self.class_fields.each { |aField|
318
- field = aField if aField.name == fieldName
319
- }
320
- field
321
- end
322
-
323
- def DomainObject.get_class_field_by_db_name( fieldName ) #:nodoc:
324
- self.class_fields.find { |field| field.db_field_name == fieldName }
325
- end
326
-
327
- # Returns an Array of ObjectField instances for this domain class, parsing
328
- # them from XML if necessary.
329
- def self.get_class_fields
330
- if self.methods( false ).include?( 'get_class_fields' )
331
- [ @@pk_fields[ self ] ]
332
- else
333
- xmlParser = try_load_xml_parser
334
- if xmlParser
335
- xmlParser.get_class_fields
336
- else
337
- error_msg = "Couldn't find either an XML class description file " +
338
- "or get_class_fields method for " + self.name
339
- raise MissingError, error_msg, caller
340
- end
341
- end
342
- end
343
-
344
- def self.get_domain_dirs #:nodoc:
345
- config = LafcadioConfig.new
346
- classPath = config['classpath']
347
- domainDirStr = config['domainDirs']
348
- if domainDirStr
349
- domainDirs = domainDirStr.split(',')
350
- else
351
- domainDirs = [ classPath + 'domain/' ]
352
- end
353
- end
354
-
355
- def self.get_field( fieldName ) #:nodoc:
356
- aDomainClass = self
357
- field = nil
358
- while aDomainClass < DomainObject && !field
359
- field = aDomainClass.get_class_field( fieldName )
360
- aDomainClass = aDomainClass.superclass
361
- end
362
- if field
363
- field
364
- else
365
- errStr = "Couldn't find field \"#{ field }\" in " +
366
- "#{ self } domain class"
367
- raise( MissingError, errStr, caller )
368
- end
369
- end
370
-
371
- def self.get_domain_class_from_string(typeString) #:nodoc:
372
- domain_class = nil
373
- require_domain_file( typeString )
374
- subclasses.each { |subclass|
375
- domain_class = subclass if subclass.to_s == typeString
376
- }
377
- if domain_class
378
- domain_class
379
- else
380
- raise CouldntMatchDomainClassError,
381
- "couldn't match domain_class #{typeString}", caller
382
- end
383
- end
384
-
385
- def self.inherited(subclass) #:nodoc:
386
- @@subclassHash[subclass] = true
387
- end
388
-
389
- def self.is_based_on? #:nodoc:
390
- self.superclass.is_concrete?
391
- end
392
-
393
- def self.is_concrete? #:nodoc:
394
- (self != DomainObject && abstract_subclasses.index(self).nil?)
395
- end
396
-
397
- def self.method_missing( methodId, *args ) #:nodoc:
398
- method_name = methodId.id2name
399
- maybe_field_class_name = ( method_name.gsub( /^(.)/ ) { $&.upcase } ) +
400
- 'Field'
401
- field_class = Lafcadio.const_get( maybe_field_class_name )
402
- create_field( field_class, args[0], args[1] || {} )
403
- end
404
-
405
- def self.domain_class #:nodoc:
406
- self
407
- end
408
-
409
- def self.require_domain_file( typeString )
410
- typeString =~ /([^\:]*)$/
411
- fileName = $1
412
- get_domain_dirs.each { |domainDir|
413
- if Dir.entries(domainDir).index("#{fileName}.rb")
414
- require "#{ domainDir }#{ fileName }"
415
- end
416
- }
417
- if (domainFilesStr = LafcadioConfig.new['domainFiles'])
418
- domainFilesStr.split(',').each { |domainFile|
419
- require domainFile
420
- }
421
- end
422
- end
423
-
424
- def self.self_and_concrete_superclasses # :nodoc:
425
- classes = [ ]
426
- a_domain_class = self
427
- until( a_domain_class == DomainObject ||
428
- abstract_subclasses.index( a_domain_class ) != nil )
429
- classes << a_domain_class
430
- a_domain_class = a_domain_class.superclass
431
- end
432
- classes
433
- end
434
-
435
- def self.singleton_method_added( symbol )
436
- if symbol.id2name == 'sql_primary_key_name' && self < DomainObject
437
- begin
438
- get_field( 'pk_id' ).db_field_name = self.send( symbol )
439
- rescue NameError
440
- @@sql_primary_keys[self] = self.send( symbol )
441
- end
442
- end
443
- end
444
-
445
- # Returns the name of the primary key in the database, retrieving it from
446
- # the class definition XML if necessary.
447
- def self.sql_primary_key_name( set_sql_primary_key_name = nil )
448
- if set_sql_primary_key_name
449
- get_field( 'pk_id' ).db_field_name = set_sql_primary_key_name
450
- end
451
- get_field( 'pk_id' ).db_field_name
452
- end
453
-
454
- def self.subclasses #:nodoc:
455
- @@subclassHash.keys
456
- end
457
-
458
- # Returns the table name, which is assumed to be the domain class name
459
- # pluralized, and with the first letter lowercase. A User class is
460
- # assumed to be stored in a "users" table, while a ProductCategory class is
461
- # assumed to be stored in a "productCategories" table.
462
- def self.table_name( set_table_name = nil )
463
- if set_table_name
464
- @table_name = set_table_name
465
- elsif @table_name
466
- @table_name
467
- else
468
- xmlParser = try_load_xml_parser
469
- if (!xmlParser.nil? && table_name = xmlParser.table_name)
470
- table_name
471
- else
472
- table_name = self.basename
473
- table_name[0] = table_name[0..0].downcase
474
- English.plural table_name
475
- end
476
- end
477
- end
478
-
479
- def self.try_load_xml_parser
480
- require 'lafcadio/domain'
481
- dirName = LafcadioConfig.new['classDefinitionDir']
482
- xmlFileName = self.basename + '.xml'
483
- xmlPath = File.join( dirName, xmlFileName )
484
- xml = ''
485
- begin
486
- File.open( xmlPath ) { |file| xml = file.readlines.join }
487
- ClassDefinitionXmlParser.new( self, xml )
488
- rescue Errno::ENOENT
489
- # no xml file, so no @xmlParser
490
- end
491
- end
492
-
493
- attr_accessor :error_messages, :last_commit, :fields, :fields_set
494
- attr_reader :delete
495
- protected :fields, :fields_set
496
-
497
- # fieldHash should contain key-value associations for the different
498
- # fields of this domain class. For example, instantiating a User class
499
- # might look like:
500
- #
501
- # User.new( 'firstNames' => 'John', 'lastName' => 'Doe',
502
- # 'email' => 'john.doe@email.com', 'password' => 'l33t' )
503
- #
504
- # In normal usage any code you write that creates a domain object will not
505
- # define the +pk_id+ field. The system assumes that a domain object with an
506
- # undefined +pk_id+ has yet to be inserted into the database, and when you
507
- # commit the domain object a +pk_id+ will automatically be assigned.
508
- #
509
- # If you're creating mock objects for unit tests, you can explicitly set
510
- # the +pk_id+ to represent objects that already exist in the database.
511
- def initialize(fieldHash)
512
- @fieldHash = fieldHash
513
- @error_messages = []
514
- @fields = {}
515
- @fields_set = []
516
- check_fields = LafcadioConfig.new()['checkFields']
517
- verify if %w( onInstantiate onAllStates ).include?( check_fields )
518
- end
519
-
520
- # Returns a clone, with all of the fields copied.
521
- def clone
522
- copy = super
523
- copy.fields = @fields.clone
524
- copy.fields_set = @fields_set.clone
525
- copy
526
- end
527
-
528
- # Commits this domain object to the database.
529
- def commit
530
- ObjectStore.get_object_store.commit self
531
- end
532
-
533
- # Set the delete value to true if you want this domain object to be deleted
534
- # from the database during its next commit.
535
- def delete=(value)
536
- if value && !pk_id
537
- raise "No point deleting an object that's not already in the DB"
538
- end
539
- @delete = value
540
- end
541
-
542
- def get_field( field ) #:nodoc:
543
- unless @fields_set.include?( field )
544
- set_field( field, @fieldHash[field.name] )
545
- end
546
- @fields[field.name]
547
- end
548
-
549
- def get_getter_field( methId ) #:nodoc:
550
- begin
551
- self.class.get_field( methId.id2name )
552
- rescue MissingError
553
- nil
554
- end
555
- end
556
-
557
- def get_setter_field( methId ) #:nodoc:
558
- if methId.id2name =~ /(.*)=$/
559
- begin
560
- self.class.get_field( $1 )
561
- rescue MissingError
562
- nil
563
- end
564
- else
565
- nil
566
- end
567
- end
568
-
569
- def method_missing( methId, *args ) #:nodoc:
570
- if ( field = get_setter_field( methId ) )
571
- set_field( field, args.first )
572
- elsif ( field = get_getter_field( methId ) )
573
- get_field( field )
574
- elsif ( methId.to_s =~ /^get_/ and
575
- ObjectStore.get_object_store.respond_to?( methId ) )
576
- args = [ self ].concat( args )
577
- ObjectStore.get_object_store.send( methId, *args )
578
- else
579
- super( methId, *args )
580
- end
581
- end
582
-
583
- # Returns the subclass of DomainObject that this instance represents.
584
- # Because of the way that proxying works, clients should call this method
585
- # instead of Object.class.
586
- def domain_class
587
- self.class.domain_class
588
- end
589
-
590
- # This template method is called before every commit. Subclasses can
591
- # override it to ensure code is executed before a commit.
592
- def pre_commit_trigger
593
- nil
594
- end
595
-
596
- # This template method is called after every commit. Subclasses can
597
- # override it to ensure code is executed after a commit.
598
- def post_commit_trigger
599
- nil
600
- end
601
-
602
- def set_field( field, value ) #:nodoc:
603
- if field.class <= LinkField
604
- if value.class != DomainObjectProxy && value
605
- value = DomainObjectProxy.new(value)
606
- end
607
- end
608
- if ( LafcadioConfig.new()['checkFields'] == 'onAllStates' &&
609
- !field.instance_of?( PrimaryKeyField ) )
610
- field.verify( value, pk_id )
611
- end
612
- @fields[field.name] = value
613
- @fields_set << field
614
- end
615
-
616
- def verify
617
- self.class.get_class_fields.each { |field|
618
- field.verify( self.send( field.name ), self.pk_id )
619
- }
620
- end
621
- end
622
-
623
- # Any domain class that is used mostly to map between two other domain
624
- # classes should be a subclass of MapObject. Subclasses of MapObject should
625
- # override MapObject.mappedTypes, returning a two-element array containing
626
- # the domain classes that the map object maps between.
627
- class MapObject < DomainObject
628
- def self.other_mapped_type(firstType) #:nodoc:
629
- types = mappedTypes
630
- if types.index(firstType) == 0
631
- types[1]
632
- else
633
- types[0]
634
- end
635
- end
636
-
637
- def self.subsidiary_map #:nodoc:
638
- nil
639
- end
640
- end
641
- end
@@ -1,13 +0,0 @@
1
- dbuser:test
2
- dbpassword:password
3
- dbname:test
4
- dbhost:localhost
5
- adminEmail:john.doe@email.com
6
- siteName:Site Name
7
- url:http://test.url
8
- classpath:lafcadio/
9
- logdir:../test/testOutput/
10
- domainDirs:../test/mock/domain/
11
- domainFiles:../test/mock/domain.rb
12
- logSql:n
13
- classDefinitionDir:../test/testData
@@ -1,379 +0,0 @@
1
- require 'delegate'
2
- require 'singleton'
3
-
4
- module Lafcadio
5
- # The Context is a singleton object that manages ContextualServices. Each
6
- # ContextualService is a service that connects in some way to external
7
- # resources: ObjectStore connects to the database; Emailer connects to SMTP,
8
- # etc.
9
- #
10
- # Context makes it easy to ensure that each ContextualService is only
11
- # instantiated once, which can be quite useful for services with expensive
12
- # creation.
13
- #
14
- # Furthermore, Context allows you to explicitly set instances for a given
15
- # service, which can be quite useful in testing. For example, once
16
- # LafcadioTestCase#setup has an instance of MockObjectStore, it calls
17
- # context.setObjectStore @mockObjectStore
18
- # which ensures that any future calls to ObjectStore.getObjectStore will
19
- # return @mockObjectStore, instead of an instance of ObjectStore connecting
20
- # test code to a live database.
21
- class Context
22
- include Singleton
23
-
24
- def initialize
25
- @resources = {}
26
- @init_procs = {}
27
- end
28
-
29
- def create_instance( service_class ) #:nodoc:
30
- if ( proc = @init_procs[service_class] )
31
- proc.call
32
- else
33
- service_class.new
34
- end
35
- end
36
-
37
- # Flushes all cached ContextualServices.
38
- def flush
39
- @resources = {}
40
- end
41
-
42
- def get_resource( service_class ) #:nodoc:
43
- resource = @resources[service_class]
44
- unless resource
45
- resource = create_instance( service_class )
46
- set_resource service_class, resource
47
- end
48
- resource
49
- end
50
-
51
- def set_init_proc( service_class, proc )
52
- @init_procs[service_class] = proc
53
- end
54
-
55
- def set_resource(service_class, resource) #:nodoc:
56
- @resources[service_class] = resource
57
- end
58
- end
59
-
60
- # A ContextualService is a service that is managed by the Context.
61
- # ContextualServices are not instantiated normally. Instead, the instance of
62
- # such a service may be retrieved by calling the method
63
- # < class name >.get< class name >
64
- #
65
- # For example: ObjectStore.getObjectStore
66
- class ContextualService
67
- def self.flush; Context.instance.set_resource( self, nil ); end
68
-
69
- def self.method_missing( methodId, *args )
70
- methodName = methodId.id2name
71
- if methodName =~ /^get_(.*)/ || methodName =~ /^set_(.*)/
72
- if methodName =~ /^get_(.*)/
73
- Context.instance.get_resource( self )
74
- else
75
- Context.instance.set_resource( self, *args )
76
- end
77
- else
78
- super
79
- end
80
- end
81
-
82
- def self.set_init_proc
83
- proc = proc { yield }
84
- Context.instance.set_init_proc( self, proc )
85
- end
86
-
87
- # ContextualServices can only be initialized through the Context instance.
88
- # Note that if you're writing your own initialize method in a child class,
89
- # you should make sure to call super() or you'll overwrite this behavior.
90
- def initialize
91
- regexp = %r{lafcadio/util\.rb.*create_instance}
92
- unless caller.any? { |line| line =~ regexp }
93
- raise ArgumentError,
94
- "#{ self.class.name.to_s } should be instantiated by calling " +
95
- self.class.name.to_s + ".get_" + self.class.name.camel_case_to_underscore,
96
- caller
97
- end
98
- end
99
- end
100
-
101
- # A collection of English-language specific utility methods.
102
- class English
103
- # Turns a camel-case string ("camel_case_to_english") to plain English ("camel
104
- # case to english"). Each word is decapitalized.
105
- def self.camel_case_to_english(camelCaseStr)
106
- words = []
107
- nextCapIndex =(camelCaseStr =~ /[A-Z]/)
108
- while nextCapIndex != nil
109
- words << $` if $`.size > 0
110
- camelCaseStr = $& + $'
111
- camelCaseStr[0] = camelCaseStr[0..0].downcase
112
- nextCapIndex =(camelCaseStr =~ /[A-Z]/)
113
- end
114
- words << camelCaseStr
115
- words.join ' '
116
- end
117
-
118
- # Turns an English language string into camel case.
119
- def self.english_to_camel_case(englishStr)
120
- cc = ""
121
- englishStr.split.each { |word|
122
- word = word.capitalize unless cc == ''
123
- cc = cc += word
124
- }
125
- cc
126
- end
127
-
128
- # Given a singular noun, returns the plural form.
129
- def self.plural(singular)
130
- consonantYPattern = Regexp.new("([^aeiou])y$", Regexp::IGNORECASE)
131
- if singular =~ consonantYPattern
132
- singular.gsub consonantYPattern, '\1ies'
133
- elsif singular =~ /[xs]$/
134
- singular + "es"
135
- else
136
- singular + "s"
137
- end
138
- end
139
-
140
- # Returns the proper noun form of a string by capitalizing most of the
141
- # words.
142
- #
143
- # Examples:
144
- # English.proper_noun("bosnia and herzegovina") ->
145
- # "Bosnia and Herzegovina"
146
- # English.proper_noun("macedonia, the former yugoslav republic of") ->
147
- # "Macedonia, the Former Yugoslav Republic of"
148
- # English.proper_noun("virgin islands, u.s.") ->
149
- # "Virgin Islands, U.S."
150
- def self.proper_noun(string)
151
- proper_noun = ""
152
- while(matchIndex = string =~ /[\. ]/)
153
- word = string[0..matchIndex-1]
154
- word = word.capitalize unless [ 'and', 'the', 'of' ].index(word) != nil
155
- proper_noun += word + $&
156
- string = string[matchIndex+1..string.length]
157
- end
158
- word = string
159
- word = word.capitalize unless [ 'and', 'the', 'of' ].index(word) != nil
160
- proper_noun += word
161
- proper_noun
162
- end
163
-
164
- # Given a format for a template sentence, generates the sentence while
165
- # accounting for details such as pluralization and whether to use "a" or
166
- # "an".
167
- # [format] The format string. Format codes are:
168
- # * %num: Number
169
- # * %is: Transitive verb. This will be turned into "is" or "are",
170
- # depending on <tt>number</tt>.
171
- # * %nam: Name. This will be rendered as either singular or
172
- # plural, depending on <tt>number</tt>.
173
- # * %a: Indefinite article. This will be turned into "a" or "an",
174
- # depending on <tt>name</tt>.
175
- # [name] The name of the object being described.
176
- # [number] The number of the objects being describes.
177
- #
178
- # Examples:
179
- # English.sentence("There %is currently %num %nam", "product category",
180
- # 0) -> "There are currently 0 product categories"
181
- # English.sentence("There %is currently %num %nam", "product category",
182
- # 1) -> "There is currently 1 product category"
183
- # English.sentence("Add %a %nam", "invoice") -> "Add an invoice"
184
- def self.sentence(format, name, number = 1)
185
- sentence = format
186
- sentence.gsub!( /%num/, number.to_s )
187
- isVerb = number == 1 ? "is" : "are"
188
- sentence.gsub!( /%is/, isVerb )
189
- name = English.plural name if number != 1
190
- sentence.gsub!( /%nam/, name )
191
- article = starts_with_vowel_sound(name) ? 'an' : 'a'
192
- sentence.gsub!( /%a/, article )
193
- sentence
194
- end
195
-
196
- def self.singular(plural)
197
- if plural =~ /(.*)ies/
198
- $1 + 'y'
199
- elsif plural =~ /(.*s)es/
200
- $1
201
- else
202
- plural =~ /(.*)s/
203
- $1
204
- end
205
- end
206
-
207
- # Does this word start with a vowel sound? "User" and "usury" don't, but
208
- # "ugly" does.
209
- def self.starts_with_vowel_sound(word)
210
- uSomethingUMatch = word =~ /^u[^aeiuo][aeiou]/
211
- # 'user' and 'usury' don't start with a vowel sound
212
- word =~ /^[aeiou]/ && !uSomethingUMatch
213
- end
214
- end
215
-
216
- # LafcadioConfig is a Hash that takes its data from the config file. You'll
217
- # have to set the location of that file before using it: Use
218
- # LafcadioConfig.set_filename.
219
- #
220
- # LafcadioConfig expects its data to be colon-delimited, one key-value pair
221
- # to a line. For example:
222
- # dbuser:user
223
- # dbpassword:password
224
- # dbname:lafcadio_test
225
- # dbhost:localhost
226
- class LafcadioConfig < Hash
227
- @@value_hash = nil
228
-
229
- def self.set_filename(filename); @@filename = filename; end
230
-
231
- def self.set_values( value_hash ); @@value_hash = value_hash; end
232
-
233
- def initialize
234
- if @@value_hash
235
- @@value_hash.each { |key, value| self[key] = value }
236
- else
237
- File.new( @@filename ).each_line { |line|
238
- line.chomp =~ /^(.*?):(.*)$/
239
- self[$1] = $2
240
- }
241
- end
242
- end
243
- end
244
-
245
- class MissingError < RuntimeError
246
- end
247
-
248
- # An ordered hash: Keys are ordered according to when they were inserted.
249
- class QueueHash < DelegateClass( Array )
250
- # Creates a QueueHash with all the elements in <tt>array</tt> as keys, and
251
- # each value initially set to be the same as the corresponding key.
252
- def self.new_from_array(array)
253
- new( *( ( array.map { |elt| [ elt, elt ] } ).flatten ) )
254
- end
255
-
256
- # Takes an even number of arguments, and sets each odd-numbered argument to
257
- # correspond to the argument immediately afterward. For example:
258
- # queueHash = QueueHash.new (1, 2, 3, 4)
259
- # queueHash[1] => 2
260
- # queueHash[3] => 4
261
- def initialize(*values)
262
- @pairs = []
263
- 0.step(values.size-1, 2) { |i| @pairs << [ values[i], values[i+1] ] }
264
- super( @pairs )
265
- end
266
-
267
- def ==( otherObj )
268
- if otherObj.class == QueueHash && otherObj.size == size
269
- ( 0..size ).all? { |i|
270
- keys[i] == otherObj.keys[i] && values[i] == otherObj.values[i]
271
- }
272
- else
273
- false
274
- end
275
- end
276
-
277
- def [](key)
278
- ( pair = @pairs.find { |pair| pair[0] == key } ) ? pair.last : nil
279
- end
280
-
281
- def []=(key, value); @pairs << [key, value]; end
282
-
283
- def each; @pairs.each { |pair| yield pair[0], pair[1] }; end
284
-
285
- def keys; @pairs.map { |pair| pair[0] }; end
286
-
287
- def values; @pairs.map { |pair| pair[1] }; end
288
- end
289
-
290
- class UsStates
291
- # Returns a QueueHash of states, with two-letter postal codes as keys and
292
- # state names as values.
293
- def self.states
294
- QueueHash.new( 'AL', 'Alabama', 'AK', 'Alaska', 'AZ', 'Arizona',
295
- 'AR', 'Arkansas', 'CA', 'California', 'CO', 'Colorado',
296
- 'CT', 'Connecticut', 'DE', 'Delaware',
297
- 'DC', 'District of Columbia', 'FL', 'Florida',
298
- 'GA', 'Georgia', 'HI', 'Hawaii', 'ID', 'Idaho',
299
- 'IL', 'Illinois', 'IN', 'Indiana', 'IA', 'Iowa',
300
- 'KS', 'Kansas', 'KY', 'Kentucky', 'LA', 'Louisiana',
301
- 'ME', 'Maine', 'MD', 'Maryland', 'MA', 'Massachusetts',
302
- 'MI', 'Michigan', 'MN', 'Minnesota', 'MS', 'Mississippi',
303
- 'MO', 'Missouri', 'MT', 'Montana', 'NE', 'Nebraska',
304
- 'NV', 'Nevada', 'NH', 'New Hampshire', 'NJ', 'New Jersey',
305
- 'NM', 'New Mexico', 'NY', 'New York',
306
- 'NC', 'North Carolina', 'ND', 'North Dakota', 'OH', 'Ohio',
307
- 'OK', 'Oklahoma', 'OR', 'Oregon', 'PA', 'Pennsylvania',
308
- 'PR', 'Puerto Rico', 'RI', 'Rhode Island',
309
- 'SC', 'South Carolina', 'SD', 'South Dakota',
310
- 'TN', 'Tennessee', 'TX', 'Texas', 'UT', 'Utah',
311
- 'VT', 'Vermont', 'VA', 'Virginia', 'WA', 'Washington',
312
- 'WV', 'West Virginia', 'WI', 'Wisconsin', 'WY', 'Wyoming' )
313
- end
314
- end
315
- end
316
-
317
- class String
318
- # Returns the underscored version of a camel-case string.
319
- def camel_case_to_underscore
320
- ( gsub( /(.)([A-Z])/ ) { $1 + '_' + $2.downcase } ).downcase
321
- end
322
-
323
- # Returns the number of times that <tt>regexp</tt> occurs in the string.
324
- def count_occurrences(regexp)
325
- count = 0
326
- str = self.clone
327
- while str =~ regexp
328
- count += 1
329
- str = $'
330
- end
331
- count
332
- end
333
-
334
- # Decapitalizes the first letter of the string, or decapitalizes the
335
- # entire string if it's all capitals.
336
- #
337
- # 'InternalClient'.decapitalize -> "internalClient"
338
- # 'SKU'.decapitalize -> "sku"
339
- def decapitalize
340
- string = clone
341
- firstLetter = string[0..0].downcase
342
- string = firstLetter + string[1..string.length]
343
- newString = ""
344
- while string =~ /([A-Z])([^a-z]|$)/
345
- newString += $`
346
- newString += $1.downcase
347
- string = $2 + $'
348
- end
349
- newString += string
350
- newString
351
- end
352
-
353
- # Turns a numeric string into U.S. format if it's not already formatted that
354
- # way.
355
- #
356
- # "10,00".numeric_string_to_us_format -> "10.00"
357
- # "10.00".numeric_string_to_us_format -> "10.00"
358
- def numeric_string_to_us_format
359
- numericString = clone
360
- numericString.gsub!(/,/, '.') if numericString =~ /,\d{2}$/
361
- numericString
362
- end
363
-
364
- # Left-pads a string with +fillChar+ up to +size+ size.
365
- #
366
- # "a".pad( 10, "+") -> "+++++++++a"
367
- def pad(size, fillChar)
368
- string = clone
369
- while string.length < size
370
- string = fillChar + string
371
- end
372
- string
373
- end
374
-
375
- # Returns the camel-case equivalent of an underscore-style string.
376
- def underscore_to_camel_case
377
- capitalize.gsub( /_([a-zA-Z0-9]+)/ ) { |s| s[1,s.size - 1].capitalize }
378
- end
379
- end