lafcadio 0.6.2 → 0.6.3
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/lafcadio.rb +1 -1
- data/lib/lafcadio.rb~ +1 -1
- data/lib/lafcadio/domain.rb +2 -0
- data/lib/lafcadio/domain.rb~ +1 -2
- data/lib/lafcadio/mock.rb +5 -3
- data/lib/lafcadio/mock.rb~ +95 -0
- data/lib/lafcadio/query.rb +8 -6
- data/lib/lafcadio/util.rb +11 -8
- data/lib/lafcadio/util.rb~ +379 -0
- metadata +4 -2
data/lib/lafcadio.rb
CHANGED
data/lib/lafcadio.rb~
CHANGED
data/lib/lafcadio/domain.rb
CHANGED
data/lib/lafcadio/domain.rb~
CHANGED
@@ -394,8 +394,7 @@ module Lafcadio
|
|
394
394
|
|
395
395
|
def self.method_missing( methodId, *args ) #:nodoc:
|
396
396
|
method_name = methodId.id2name
|
397
|
-
maybe_field_class_name =
|
398
|
-
'Field'
|
397
|
+
maybe_field_class_name = method_name.underscore_to_camel_case + 'Field'
|
399
398
|
field_class = Lafcadio.const_get( maybe_field_class_name )
|
400
399
|
create_field( field_class, args[0], args[1] || {} )
|
401
400
|
end
|
data/lib/lafcadio/mock.rb
CHANGED
@@ -37,12 +37,14 @@ module Lafcadio
|
|
37
37
|
objects << dbObj
|
38
38
|
end
|
39
39
|
}
|
40
|
-
if (range = query.limit)
|
41
|
-
objects = objects[0..(range.last - range.first)]
|
42
|
-
end
|
43
40
|
if ( order_by = query.order_by )
|
44
41
|
objects = objects.sort_by { |dobj| dobj.send( order_by ) }
|
45
42
|
objects.reverse! if query.order_by_order == Query::DESC
|
43
|
+
else
|
44
|
+
objects = objects.sort_by { |dobj| dobj.pk_id }
|
45
|
+
end
|
46
|
+
if (range = query.limit)
|
47
|
+
objects = objects[0..(range.last - range.first)]
|
46
48
|
end
|
47
49
|
objects
|
48
50
|
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'lafcadio/objectStore'
|
2
|
+
|
3
|
+
module Lafcadio
|
4
|
+
class MockDbBridge #:nodoc:
|
5
|
+
attr_reader :last_pk_id_inserted, :retrievals_by_type, :query_count
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@objects = {}
|
9
|
+
@retrievals_by_type = Hash.new 0
|
10
|
+
@query_count = Hash.new( 0 )
|
11
|
+
end
|
12
|
+
|
13
|
+
def commit(db_object)
|
14
|
+
objects_by_domain_class = get_objects_by_domain_class(
|
15
|
+
db_object.domain_class
|
16
|
+
)
|
17
|
+
if db_object.delete
|
18
|
+
objects_by_domain_class.delete( db_object.pk_id )
|
19
|
+
else
|
20
|
+
object_pk_id = get_pk_id_before_committing( db_object )
|
21
|
+
objects_by_domain_class[object_pk_id] = db_object
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def _get_all( domain_class )
|
26
|
+
@retrievals_by_type[domain_class] = @retrievals_by_type[domain_class] + 1
|
27
|
+
@objects[domain_class] ? @objects[domain_class].values : []
|
28
|
+
end
|
29
|
+
|
30
|
+
def get_collection_by_query(query)
|
31
|
+
@query_count[query] += 1
|
32
|
+
objects = []
|
33
|
+
_get_all( query.domain_class ).each { |dbObj|
|
34
|
+
if query.condition
|
35
|
+
objects << dbObj if query.condition.object_meets(dbObj)
|
36
|
+
else
|
37
|
+
objects << dbObj
|
38
|
+
end
|
39
|
+
}
|
40
|
+
if ( order_by = query.order_by )
|
41
|
+
objects = objects.sort_by { |dobj| dobj.send( order_by ) }
|
42
|
+
objects.reverse! if query.order_by_order == Query::DESC
|
43
|
+
end
|
44
|
+
if (range = query.limit)
|
45
|
+
objects = objects[0..(range.last - range.first)]
|
46
|
+
end
|
47
|
+
objects
|
48
|
+
end
|
49
|
+
|
50
|
+
def get_pk_id_before_committing( db_object )
|
51
|
+
object_pk_id = db_object.pk_id
|
52
|
+
unless object_pk_id
|
53
|
+
maxpk_id = 0
|
54
|
+
pk_ids = get_objects_by_domain_class( db_object.domain_class ).keys
|
55
|
+
pk_ids.each { |pk_id|
|
56
|
+
maxpk_id = pk_id if pk_id > maxpk_id
|
57
|
+
}
|
58
|
+
@last_pk_id_inserted = maxpk_id + 1
|
59
|
+
object_pk_id = @last_pk_id_inserted
|
60
|
+
end
|
61
|
+
object_pk_id
|
62
|
+
end
|
63
|
+
|
64
|
+
def get_objects_by_domain_class( domain_class )
|
65
|
+
objects_by_domain_class = @objects[domain_class]
|
66
|
+
unless objects_by_domain_class
|
67
|
+
objects_by_domain_class = {}
|
68
|
+
@objects[domain_class] = objects_by_domain_class
|
69
|
+
end
|
70
|
+
objects_by_domain_class
|
71
|
+
end
|
72
|
+
|
73
|
+
def group_query( query )
|
74
|
+
if query.class == Query::Max
|
75
|
+
if ( query.field_name == 'pk_id' || query.field_name == 'rate' )
|
76
|
+
query.collect( @objects[query.domain_class].values )
|
77
|
+
else
|
78
|
+
raise "Can't handle query with sql '#{ query.to_sql }'"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Externally, the MockObjectStore looks and acts exactly like the ObjectStore,
|
85
|
+
# but stores all its data in memory. This makes it very useful for unit
|
86
|
+
# testing, and in fact LafcadioTestCase#setup creates a new instance of
|
87
|
+
# MockObjectStore for each test case.
|
88
|
+
class MockObjectStore < ObjectStore
|
89
|
+
public_class_method :new
|
90
|
+
|
91
|
+
def initialize # :nodoc:
|
92
|
+
super( MockDbBridge.new )
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
data/lib/lafcadio/query.rb
CHANGED
@@ -273,15 +273,17 @@ module Lafcadio
|
|
273
273
|
AND = 1
|
274
274
|
OR = 2
|
275
275
|
|
276
|
-
def initialize(*
|
277
|
-
if( [ AND, OR ].index(
|
278
|
-
@compoundType =
|
279
|
-
|
276
|
+
def initialize( *args )
|
277
|
+
if( [ AND, OR ].index( args.last) )
|
278
|
+
@compoundType = args.last
|
279
|
+
args.pop
|
280
280
|
else
|
281
281
|
@compoundType = AND
|
282
282
|
end
|
283
|
-
@conditions =
|
284
|
-
|
283
|
+
@conditions = args.map { |arg|
|
284
|
+
arg.respond_to?( :to_condition ) ? arg.to_condition : arg
|
285
|
+
}
|
286
|
+
@domain_class = @conditions[0].domain_class
|
285
287
|
end
|
286
288
|
|
287
289
|
def object_meets(anObj)
|
data/lib/lafcadio/util.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'delegate'
|
2
|
+
require 'lafcadio/depend'
|
2
3
|
require 'singleton'
|
3
4
|
|
4
5
|
module Lafcadio
|
@@ -66,14 +67,16 @@ module Lafcadio
|
|
66
67
|
class ContextualService
|
67
68
|
def self.flush; Context.instance.set_resource( self, nil ); end
|
68
69
|
|
69
|
-
def self.method_missing(
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
70
|
+
def self.method_missing( symbol, *args )
|
71
|
+
method_name = symbol.id2name
|
72
|
+
target = nil
|
73
|
+
if method_name =~ /^get_(.*)/
|
74
|
+
target = :get_resource if $1.underscore_to_camel_case == basename
|
75
|
+
elsif method_name =~ /^set_(.*)/
|
76
|
+
target = :set_resource if $1.underscore_to_camel_case == basename
|
77
|
+
end
|
78
|
+
if target
|
79
|
+
Context.instance.send( target, self, *args )
|
77
80
|
else
|
78
81
|
super
|
79
82
|
end
|
@@ -0,0 +1,379 @@
|
|
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
|
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-
|
6
|
+
version: 0.6.3
|
7
|
+
date: 2005-02-26
|
8
8
|
summary: Lafcadio is an object-relational mapping layer
|
9
9
|
require_paths:
|
10
10
|
- lib
|
@@ -38,6 +38,7 @@ files:
|
|
38
38
|
- lib/lafcadio/domain.rb
|
39
39
|
- lib/lafcadio/domain.rb~
|
40
40
|
- lib/lafcadio/mock.rb
|
41
|
+
- lib/lafcadio/mock.rb~
|
41
42
|
- lib/lafcadio/objectField.rb
|
42
43
|
- lib/lafcadio/objectField.rb~
|
43
44
|
- lib/lafcadio/objectStore.rb
|
@@ -48,6 +49,7 @@ files:
|
|
48
49
|
- lib/lafcadio/test
|
49
50
|
- lib/lafcadio/test.rb
|
50
51
|
- lib/lafcadio/util.rb
|
52
|
+
- lib/lafcadio/util.rb~
|
51
53
|
- lib/lafcadio/test/testconfig.dat
|
52
54
|
test_files: []
|
53
55
|
rdoc_options: []
|