lafcadio 0.6.0 → 0.6.1

Sign up to get free protection for your applications and to get access to all the features.
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