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 +1 -2
- data/lib/lafcadio.rb~ +1 -2
- metadata +2 -5
- data/lib/lafcadio/domain.rb~ +0 -641
- data/lib/lafcadio/test/testconfig.dat~ +0 -13
- data/lib/lafcadio/util.rb~ +0 -379
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.
|
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.
|
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.
|
7
|
-
date: 2005-01-
|
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: []
|
data/lib/lafcadio/domain.rb~
DELETED
@@ -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
|
data/lib/lafcadio/util.rb~
DELETED
@@ -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
|