lafcadio 0.6.2 → 0.6.3

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 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: []