lafcadio 0.8.0 → 0.8.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.
@@ -1,108 +0,0 @@
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
- if db_object.pk_id and !db_object.pk_id.is_a?( Integer )
15
- raise ArgumentError
16
- end
17
- objects_by_domain_class = get_objects_by_domain_class(
18
- db_object.domain_class
19
- )
20
- if db_object.delete
21
- objects_by_domain_class.delete( db_object.pk_id )
22
- else
23
- object_pk_id = get_pk_id_before_committing( db_object )
24
- objects_by_domain_class[object_pk_id] = db_object
25
- end
26
- end
27
-
28
- def _get_all( domain_class )
29
- @retrievals_by_type[domain_class] = @retrievals_by_type[domain_class] + 1
30
- @objects[domain_class] ? @objects[domain_class].values : []
31
- end
32
-
33
- def get_collection_by_query(query)
34
- @query_count[query.to_sql] += 1
35
- objects = []
36
- _get_all( query.domain_class ).each { |dbObj|
37
- if query.condition
38
- objects << dbObj if query.condition.object_meets(dbObj)
39
- else
40
- objects << dbObj
41
- end
42
- }
43
- if ( order_by = query.order_by )
44
- objects = objects.sort_by { |dobj| dobj.send( order_by ) }
45
- objects.reverse! if query.order_by_order == Query::DESC
46
- else
47
- objects = objects.sort_by { |dobj| dobj.pk_id }
48
- end
49
- if (range = query.limit)
50
- objects = objects[range]
51
- end
52
- objects
53
- end
54
-
55
- def get_pk_id_before_committing( db_object )
56
- object_pk_id = db_object.pk_id
57
- unless object_pk_id
58
- maxpk_id = 0
59
- pk_ids = get_objects_by_domain_class( db_object.domain_class ).keys
60
- pk_ids.each { |pk_id|
61
- maxpk_id = pk_id if pk_id > maxpk_id
62
- }
63
- @last_pk_id_inserted = maxpk_id + 1
64
- object_pk_id = @last_pk_id_inserted
65
- end
66
- object_pk_id
67
- end
68
-
69
- def get_objects_by_domain_class( domain_class )
70
- objects_by_domain_class = @objects[domain_class]
71
- unless objects_by_domain_class
72
- objects_by_domain_class = {}
73
- @objects[domain_class] = objects_by_domain_class
74
- end
75
- objects_by_domain_class
76
- end
77
-
78
- def group_query( query )
79
- if query.class == Query::Max
80
- dobjs_by_pk_id = @objects[query.domain_class]
81
- if dobjs_by_pk_id
82
- dobjs = dobjs_by_pk_id.values.sort_by { |dobj|
83
- dobj.send( query.field_name )
84
- }
85
- [ dobjs.last.send( query.field_name ) ]
86
- else
87
- [ nil ]
88
- end
89
- end
90
- end
91
- end
92
-
93
- # Externally, the MockObjectStore looks and acts exactly like the ObjectStore,
94
- # but stores all its data in memory. This makes it very useful for unit
95
- # testing, and in fact LafcadioTestCase#setup creates a new instance of
96
- # MockObjectStore for each test case.
97
- class MockObjectStore < ObjectStore
98
- public_class_method :new
99
-
100
- def initialize # :nodoc:
101
- super( MockDbBridge.new )
102
- end
103
-
104
- def mock? # :nodoc:
105
- true
106
- end
107
- end
108
- end
@@ -1,564 +0,0 @@
1
- require 'date'
2
- require 'lafcadio/depend'
3
- require 'lafcadio/util'
4
-
5
- module Lafcadio
6
- # ObjectField is the abstract base class of any field for domain objects.
7
- class ObjectField
8
- include Comparable
9
-
10
- attr_reader :name, :domain_class
11
- attr_accessor :not_null, :db_field_name
12
-
13
- def self.instantiate_from_xml( domain_class, fieldElt ) #:nodoc:
14
- parameters = instantiation_parameters( fieldElt )
15
- instantiate_with_parameters( domain_class, parameters )
16
- end
17
-
18
- def self.instantiate_with_parameters( domain_class, parameters ) #:nodoc:
19
- instance = self.new( domain_class, parameters['name'] )
20
- if ( db_field_name = parameters['db_field_name'] )
21
- instance.db_field_name = db_field_name
22
- end
23
- instance
24
- end
25
-
26
- def self.instantiation_parameters( fieldElt ) #:nodoc:
27
- parameters = {}
28
- parameters['name'] = fieldElt.attributes['name']
29
- parameters['db_field_name'] = fieldElt.attributes['db_field_name']
30
- parameters
31
- end
32
-
33
- def self.value_type #:nodoc:
34
- Object
35
- end
36
-
37
- # [domain_class] The domain class that this object field belongs to.
38
- # [name] The name of this field.
39
- def initialize( domain_class, name )
40
- @domain_class = domain_class
41
- @name = name
42
- @db_field_name = name
43
- @not_null = true
44
- end
45
-
46
- def <=>(other)
47
- if @domain_class == other.domain_class && name == other.name
48
- 0
49
- else
50
- object_id <=> other.object_id
51
- end
52
- end
53
-
54
- def bind_write?; false; end #:nodoc:
55
-
56
- def db_table_and_field_name
57
- "#{ domain_class.table_name }.#{ db_field_name }"
58
- end
59
-
60
- def db_will_automatically_write #:nodoc:
61
- false
62
- end
63
-
64
- # Returns the name that this field is referenced by in the MySQL table. By
65
- # default this is the same as the name; to override it, set
66
- # ObjectField#db_field_name.
67
- def name_for_sql
68
- db_field_name
69
- end
70
-
71
- def prev_value(pk_id) #:nodoc:
72
- prevObject = ObjectStore.get_object_store.get( @domain_class, pk_id )
73
- prevObject.send(name)
74
- end
75
-
76
- def process_before_verify(value) #:nodoc:
77
- value = @default if value == nil
78
- value
79
- end
80
-
81
- # Returns a string value suitable for committing this field's value to
82
- # MySQL.
83
- def value_for_sql(value)
84
- value || 'null'
85
- end
86
-
87
- def verify(value, pk_id) #:nodoc:
88
- if value.nil? && not_null
89
- raise(
90
- FieldValueError,
91
- "#{ self.domain_class.name }##{ name } can not be nil.",
92
- caller
93
- )
94
- end
95
- verify_non_nil( value, pk_id ) if value
96
- end
97
-
98
- def verify_non_nil( value, pk_id )
99
- value_type = self.class.value_type
100
- unless value.class <= value_type
101
- raise(
102
- FieldValueError,
103
- "#{ domain_class.name }##{ name } needs a " + value_type.name +
104
- " value.",
105
- caller
106
- )
107
- end
108
- end
109
-
110
- # Given the SQL value string, returns a Ruby-native value.
111
- def value_from_sql(string)
112
- string
113
- end
114
- end
115
-
116
- # A StringField is expected to contain a string value.
117
- class StringField < ObjectField
118
- def value_for_sql(value) #:nodoc:
119
- if value
120
- value = value.gsub(/(\\?')/) { |m| m.length == 1 ? "''" : m }
121
- value = value.gsub(/\\/) { '\\\\' }
122
- "'#{value}'"
123
- else
124
- "null"
125
- end
126
- end
127
- end
128
-
129
- # IntegerField represents an integer.
130
- class IntegerField < ObjectField
131
- def value_from_sql(string) #:nodoc:
132
- value = super
133
- value ? value.to_i : nil
134
- end
135
- end
136
-
137
- # BlobField stores a string value and expects to store its value in a BLOB
138
- # field in the database.
139
- class BlobField < ObjectField
140
- attr_accessor :size
141
-
142
- def self.value_type; String; end
143
-
144
- def bind_write?; true; end #:nodoc:
145
-
146
- def value_for_sql(value); "?"; end #:nodoc:
147
- end
148
-
149
- # BooleanField represents a boolean value. By default, it assumes that the
150
- # table field represents True and False with the integers 1 and 0. There are
151
- # two different ways to change this default.
152
- #
153
- # First, BooleanField includes a few enumerated defaults. Currently there are
154
- # only
155
- # * BooleanField::ENUMS_ONE_ZERO (the default, uses integers 1 and 0)
156
- # * BooleanField::ENUMS_CAPITAL_YES_NO (uses characters 'Y' and 'N')
157
- # In the XML class definition, this field would look like
158
- # <field name="field_name" class="BooleanField"
159
- # enum_type="ENUMS_CAPITAL_YES_NO"/>
160
- # If you're defining a field in Ruby, simply set BooleanField#enum_type to one
161
- # of the values.
162
- #
163
- # For more fine-grained specification you can pass specific values in. Use
164
- # this format for the XML class definition:
165
- # <field name="field_name" class="BooleanField">
166
- # <enums>
167
- # <enum key="true">yin</enum>
168
- # <enum key="false">tang</enum>
169
- # </enums>
170
- # </field>
171
- # If you're defining the field in Ruby, set BooleanField#enums to a hash.
172
- # myBooleanField.enums = { true => 'yin', false => 'yang' }
173
- #
174
- # +enums+ takes precedence over +enum_type+.
175
- class BooleanField < ObjectField
176
- ENUMS_ONE_ZERO = 0
177
- ENUMS_CAPITAL_YES_NO = 1
178
-
179
- attr_accessor :enum_type, :enums
180
-
181
- def initialize( domain_class, name )
182
- super( domain_class, name )
183
- @enum_type = ENUMS_ONE_ZERO
184
- @enums = nil
185
- end
186
-
187
- def false_enum # :nodoc:
188
- get_enums[false]
189
- end
190
-
191
- def get_enums( value = nil ) # :nodoc:
192
- if @enums
193
- @enums
194
- elsif @enum_type == ENUMS_ONE_ZERO
195
- if value.class == String
196
- { true => '1', false => '0' }
197
- else
198
- { true => 1, false => 0 }
199
- end
200
- elsif @enum_type == ENUMS_CAPITAL_YES_NO
201
- { true => 'Y', false => 'N' }
202
- else
203
- raise MissingError
204
- end
205
- end
206
-
207
- def text_enum_type # :nodoc:
208
- @enums ? @enums[true].class == String : @enum_type == ENUMS_CAPITAL_YES_NO
209
- end
210
-
211
- def true_enum( value = nil ) # :nodoc:
212
- get_enums( value )[true]
213
- end
214
-
215
- def value_for_sql(value) # :nodoc:
216
- if value
217
- vfs = true_enum
218
- else
219
- vfs = false_enum
220
- end
221
- text_enum_type ? "'#{vfs}'" : vfs
222
- end
223
-
224
- def value_from_sql(value, lookupLink = true) # :nodoc:
225
- value == true_enum( value )
226
- end
227
- end
228
-
229
- # DateField represents a Date.
230
- class DateField < ObjectField
231
- def self.value_type # :nodoc:
232
- Date
233
- end
234
-
235
- def initialize( domain_class, name = "date" )
236
- super( domain_class, name )
237
- end
238
-
239
- def value_for_sql(value) # :nodoc:
240
- value ? "'#{value.to_s}'" : 'null'
241
- end
242
-
243
- def value_from_sql(dbiDate, lookupLink = true) # :nodoc:
244
- begin
245
- dbiDate ? dbiDate.to_date : nil
246
- rescue ArgumentError
247
- nil
248
- end
249
- end
250
- end
251
-
252
- # DateTimeField represents a DateTime.
253
- class DateTimeField < ObjectField
254
- def value_for_sql(value) # :nodoc:
255
- if value
256
- year = value.year
257
- month = value.mon.to_s.pad( 2, "0" )
258
- day = value.day.to_s.pad( 2, "0" )
259
- hour = value.hour.to_s.pad( 2, "0" )
260
- minute = value.min.to_s.pad( 2, "0" )
261
- second = value.sec.to_s.pad( 2, "0" )
262
- "'#{year}-#{month}-#{day} #{hour}:#{minute}:#{second}'"
263
- else
264
- "null"
265
- end
266
- end
267
-
268
- def value_from_sql(dbi_value, lookupLink = true) # :nodoc:
269
- dbi_value ? dbi_value.to_time : nil
270
- end
271
- end
272
-
273
- # A DomainObjectField is used to link from one domain class to another.
274
- class DomainObjectField < ObjectField
275
- def self.auto_name( linked_type )
276
- linked_type.name =~ /::/
277
- ( $' || linked_type.name ).camel_case_to_underscore
278
- end
279
-
280
- def self.instantiate_with_parameters( domain_class, parameters ) #:nodoc:
281
- linked_type = parameters['linked_type']
282
- instance = self.new(
283
- domain_class, linked_type,
284
- parameters['name'] || auto_name( linked_type ),
285
- parameters['delete_cascade']
286
- )
287
- if parameters['db_field_name']
288
- instance.db_field_name = parameters['db_field_name']
289
- end
290
- instance
291
- end
292
-
293
- def self.instantiation_parameters( fieldElt ) #:nodoc:
294
- parameters = super( fieldElt )
295
- linked_typeStr = fieldElt.attributes['linked_type']
296
- linked_type = DomainObject.get_domain_class_from_string( linked_typeStr )
297
- parameters['linked_type'] = linked_type
298
- parameters['delete_cascade'] = fieldElt.attributes['delete_cascade'] == 'y'
299
- parameters
300
- end
301
-
302
- attr_reader :linked_type
303
- attr_accessor :delete_cascade
304
-
305
- # [domain_class] The domain class that this field belongs to.
306
- # [linked_type] The domain class that this field points to.
307
- # [name] The name of this field.
308
- # [delete_cascade] If this is true, deleting the domain object that is
309
- # linked to will cause this domain object to be deleted
310
- # as well.
311
- def initialize( domain_class, linked_type, name = nil,
312
- delete_cascade = false )
313
- name = self.class.auto_name( linked_type ) unless name
314
- super( domain_class, name )
315
- ( @linked_type, @delete_cascade ) = linked_type, delete_cascade
316
- end
317
-
318
- def value_from_sql(string) #:nodoc:
319
- string != nil ? DomainObjectProxy.new(@linked_type, string.to_i) : nil
320
- end
321
-
322
- def value_for_sql(value) #:nodoc:
323
- if !value
324
- "null"
325
- elsif value.pk_id
326
- value.pk_id
327
- else
328
- raise( DomainObjectInitError, "Can't commit #{name} without pk_id",
329
- caller )
330
- end
331
- end
332
-
333
- def verify_non_nil(value, pk_id) #:nodoc:
334
- super
335
- if @linked_type != @domain_class && pk_id
336
- subsetDomainObjectField = @linked_type.class_fields.find { |field|
337
- field.class == SubsetDomainObjectField && field.subset_field == @name
338
- }
339
- if subsetDomainObjectField
340
- verify_subset_link_field( subsetDomainObjectField, pk_id )
341
- end
342
- end
343
- end
344
-
345
- def verify_subset_link_field( subsetDomainObjectField, pk_id )
346
- begin
347
- prevObj = ObjectStore.get_object_store.get( domain_class, pk_id )
348
- prevObjLinkedTo = prevObj.send(name)
349
- possiblyMyObj = prevObjLinkedTo.send(subsetDomainObjectField.name)
350
- if possiblyMyObj && possiblyMyObj.pk_id == pk_id
351
- cantChangeMsg = "You can't change that."
352
- raise FieldValueError, cantChangeMsg, caller
353
- end
354
- rescue DomainObjectNotFoundError
355
- # no previous value, so nothing to check for
356
- end
357
- end
358
- end
359
-
360
- # EmailField takes a text value that is expected to be formatted as a single
361
- # valid email address.
362
- class EmailField < StringField
363
- # Is +address+ a valid email address?
364
- def self.valid_address(address)
365
- address =~ /^[^ @]+@[^ \.]+\.[^ ,]+$/
366
- end
367
-
368
- def initialize( domain_class, name = "email" )
369
- super( domain_class, name )
370
- end
371
-
372
- def verify_non_nil(value, pk_id) #:nodoc:
373
- super(value, pk_id)
374
- if !EmailField.valid_address(value)
375
- raise(
376
- FieldValueError,
377
- "#{ domain_class.name }##{ name } needs a valid email address.",
378
- caller
379
- )
380
- end
381
- end
382
- end
383
-
384
- # EnumField represents an enumerated field that can only be set to one of a
385
- # set range of string values. To set the enumeration in the class definition
386
- # XML, use the following format:
387
- # <field name="flavor" class="EnumField">
388
- # <enums>
389
- # <enum>Vanilla</enum>
390
- # <enum>Chocolate</enum>
391
- # <enum>Lychee</enum>
392
- # </enums>
393
- # </field>
394
- # If you're defining the field in Ruby, you can simply pass in an array of
395
- # enums as the +enums+ argument.
396
- #
397
- class EnumField < StringField
398
- def self.instantiate_with_parameters( domain_class, parameters ) #:nodoc:
399
- self.new( domain_class, parameters['name'], parameters['enums'] )
400
- end
401
-
402
- def self.enum_queue_hash( fieldElt )
403
- enumValues = []
404
- fieldElt.elements.each( 'enums/enum' ) { |enumElt|
405
- enumValues << enumElt.attributes['key']
406
- enumValues << enumElt.text.to_s
407
- }
408
- QueueHash.new( *enumValues )
409
- end
410
-
411
- def self.instantiation_parameters( fieldElt ) #:nodoc:
412
- parameters = super( fieldElt )
413
- if fieldElt.elements['enums'][1].attributes['key']
414
- parameters['enums'] = enum_queue_hash( fieldElt )
415
- else
416
- parameters['enums'] = []
417
- fieldElt.elements.each( 'enums/enum' ) { |enumElt|
418
- parameters['enums'] << enumElt.text.to_s
419
- }
420
- end
421
- parameters
422
- end
423
-
424
- attr_reader :enums
425
-
426
- # [domain_class] The domain class that this field belongs to.
427
- # [name] The name of this domain class.
428
- # [enums] An array of Strings representing the possible choices for
429
- # this field.
430
- def initialize( domain_class, name, enums )
431
- super( domain_class, name )
432
- if enums.class == Array
433
- @enums = QueueHash.new_from_array enums
434
- else
435
- @enums = enums
436
- end
437
- end
438
-
439
- def value_for_sql(value) #:nodoc:
440
- value != '' ?(super(value)) : 'null'
441
- end
442
-
443
- def verify_non_nil( value, pk_id ) #:nodoc:
444
- super
445
- if @enums[value].nil?
446
- key_str = '[ ' +
447
- ( @enums.keys.map { |key| "\"#{ key }\"" } ).join(', ') + ' ]'
448
- err_str = "#{ @domain_class.name }##{ name } needs a value that is " +
449
- "one of #{ key_str }"
450
- raise( FieldValueError, err_str, caller )
451
- end
452
- end
453
- end
454
-
455
- class FieldValueError < RuntimeError #:nodoc:
456
- end
457
-
458
- # FloatField represents a decimal value.
459
- class FloatField < ObjectField
460
- def self.instantiate_with_parameters( domain_class, parameters ) #:nodoc:
461
- self.new( domain_class, parameters['name'] )
462
- end
463
-
464
- def self.value_type #:nodoc:
465
- Numeric
466
- end
467
-
468
- def process_before_verify(value) #:nodoc:
469
- value = super value
470
- value != nil && value != '' ? value.to_f : nil
471
- end
472
-
473
- def value_from_sql(string, lookupLink = true) #:nodoc:
474
- string != nil ? string.to_f : nil
475
- end
476
- end
477
-
478
- # Accepts a Month as a value. This field automatically saves in MySQL as a
479
- # date corresponding to the first day of the month.
480
- class MonthField < DateField
481
- def self.value_type #:nodoc:
482
- Month
483
- end
484
-
485
- def value_for_sql(value) #:nodoc:
486
- "'#{value.year}-#{value.month}-01'"
487
- end
488
- end
489
-
490
- class PrimaryKeyField < IntegerField
491
- def initialize( domain_class )
492
- super( domain_class, 'pk_id' )
493
- @not_null = false
494
- end
495
- end
496
-
497
- # A StateField is a specialized subclass of EnumField; its possible values are
498
- # any of the 50 states of the United States, stored as each state's two-letter
499
- # postal code.
500
- class StateField < EnumField
501
- def initialize( domain_class, name = "state" )
502
- super( domain_class, name, UsCommerce::UsStates.states )
503
- end
504
- end
505
-
506
- class SubsetDomainObjectField < DomainObjectField #:nodoc:
507
- def self.instantiate_with_parameters( domain_class, parameters )
508
- self.new( domain_class, parameters['linked_type'],
509
- parameters['subset_field'], parameters['name'] )
510
- end
511
-
512
- def self.instantiation_parameters( fieldElt )
513
- parameters = super( fieldElt )
514
- parameters['subset_field'] = fieldElt.attributes['subset_field']
515
- parameters
516
- end
517
-
518
- attr_accessor :subset_field
519
-
520
- def initialize( domain_class, linked_type, subset_field,
521
- name = linked_type.name.downcase )
522
- super( domain_class, linked_type, name )
523
- @subset_field = subset_field
524
- end
525
- end
526
-
527
- # TextListField maps to any String SQL field that tries to represent a
528
- # quick-and-dirty list with a comma-separated string. It returns an Array.
529
- # For example, a SQL field with the value "john,bill,dave", then the Ruby
530
- # field will have the value <tt>[ "john", "bill", "dave" ]</tt>.
531
- class TextListField < ObjectField
532
- def self.value_type #:nodoc:
533
- Array
534
- end
535
-
536
- def value_for_sql(objectValue) #:nodoc:
537
- if objectValue.is_a?( Array )
538
- str = objectValue.join(',')
539
- else
540
- str = objectValue
541
- end
542
- "'" + str + "'"
543
- end
544
-
545
- def value_from_sql(sqlString, lookupLink = true) #:nodoc:
546
- if sqlString
547
- sqlString.split ','
548
- else
549
- []
550
- end
551
- end
552
- end
553
-
554
- class TimeStampField < DateTimeField #:nodoc:
555
- def initialize( domain_class, name = 'timeStamp' )
556
- super( domain_class, name )
557
- @not_null = false
558
- end
559
-
560
- def db_will_automatically_write
561
- true
562
- end
563
- end
564
- end