lafcadio 0.8.0 → 0.8.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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