lafcadio 0.4.3 → 0.5.2

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