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 CHANGED
@@ -16,7 +16,7 @@
16
16
  # http://lafcadio.rubyforge.org/tutorial.html.
17
17
 
18
18
  module Lafcadio
19
- Version = "0.6.2"
19
+ Version = "0.6.3"
20
20
 
21
21
  require 'lafcadio/dateTime'
22
22
  require 'lafcadio/depend'
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.1"
19
+ Version = "0.6.2"
20
20
 
21
21
  require 'lafcadio/dateTime'
22
22
  require 'lafcadio/depend'
@@ -330,6 +330,8 @@ module Lafcadio
330
330
  def self.get_class_fields
331
331
  if self.methods( false ).include?( 'get_class_fields' )
332
332
  [ @@pk_fields[ self ] ]
333
+ elsif abstract_subclasses.include?( self )
334
+ []
333
335
  else
334
336
  xmlParser = try_load_xml_parser
335
337
  if xmlParser
@@ -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 = ( method_name.gsub( /^(.)/ ) { $&.upcase } ) +
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
@@ -273,15 +273,17 @@ module Lafcadio
273
273
  AND = 1
274
274
  OR = 2
275
275
 
276
- def initialize(*conditions)
277
- if( [ AND, OR ].index(conditions.last) )
278
- @compoundType = conditions.last
279
- conditions.pop
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 = conditions
284
- @domain_class = conditions[0].domain_class
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( 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
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.2
7
- date: 2005-01-27
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: []