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 +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
|