lafcadio 0.4.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. data/lib/lafcadio.rb +32 -0
  2. data/lib/lafcadio.rb~ +32 -0
  3. data/lib/lafcadio/TestSuite.rb +16 -0
  4. data/lib/lafcadio/dateTime.rb +2 -0
  5. data/lib/lafcadio/dateTime/Month.rb +93 -0
  6. data/lib/lafcadio/domain.rb +119 -0
  7. data/lib/lafcadio/domain.rb~ +119 -0
  8. data/lib/lafcadio/domain/DomainObject.rb +375 -0
  9. data/lib/lafcadio/domain/DomainObject.rb~ +371 -0
  10. data/lib/lafcadio/domain/MapObject.rb +22 -0
  11. data/lib/lafcadio/domain/ObjectType.rb +80 -0
  12. data/lib/lafcadio/includer.rb +18 -0
  13. data/lib/lafcadio/mock.rb +2 -0
  14. data/lib/lafcadio/mock/MockDbBridge.rb +78 -0
  15. data/lib/lafcadio/mock/MockDbBridge.rb~ +74 -0
  16. data/lib/lafcadio/mock/MockObjectStore.rb +20 -0
  17. data/lib/lafcadio/objectField.rb +14 -0
  18. data/lib/lafcadio/objectField/AutoIncrementField.rb +25 -0
  19. data/lib/lafcadio/objectField/BooleanField.rb +83 -0
  20. data/lib/lafcadio/objectField/DateField.rb +33 -0
  21. data/lib/lafcadio/objectField/DateTimeField.rb +25 -0
  22. data/lib/lafcadio/objectField/DecimalField.rb +41 -0
  23. data/lib/lafcadio/objectField/EmailField.rb +28 -0
  24. data/lib/lafcadio/objectField/EnumField.rb +62 -0
  25. data/lib/lafcadio/objectField/FieldValueError.rb +4 -0
  26. data/lib/lafcadio/objectField/IntegerField.rb +15 -0
  27. data/lib/lafcadio/objectField/LinkField.rb +92 -0
  28. data/lib/lafcadio/objectField/LinkField.rb~ +86 -0
  29. data/lib/lafcadio/objectField/MoneyField.rb +13 -0
  30. data/lib/lafcadio/objectField/MonthField.rb +16 -0
  31. data/lib/lafcadio/objectField/ObjectField.rb +142 -0
  32. data/lib/lafcadio/objectField/PasswordField.rb +29 -0
  33. data/lib/lafcadio/objectField/StateField.rb +13 -0
  34. data/lib/lafcadio/objectField/SubsetLinkField.rb +25 -0
  35. data/lib/lafcadio/objectField/TextField.rb +23 -0
  36. data/lib/lafcadio/objectField/TextListField.rb +21 -0
  37. data/lib/lafcadio/objectField/TimeStampField.rb +15 -0
  38. data/lib/lafcadio/objectStore.rb +100 -0
  39. data/lib/lafcadio/objectStore/Cache.rb +81 -0
  40. data/lib/lafcadio/objectStore/Committer.rb +65 -0
  41. data/lib/lafcadio/objectStore/CouldntMatchObjectTypeError.rb +4 -0
  42. data/lib/lafcadio/objectStore/DbBridge.rb +140 -0
  43. data/lib/lafcadio/objectStore/DbBridge.rb~ +140 -0
  44. data/lib/lafcadio/objectStore/DomainComparable.rb +25 -0
  45. data/lib/lafcadio/objectStore/DomainObjectInitError.rb +9 -0
  46. data/lib/lafcadio/objectStore/DomainObjectNotFoundError.rb +4 -0
  47. data/lib/lafcadio/objectStore/DomainObjectProxy.rb +62 -0
  48. data/lib/lafcadio/objectStore/DomainObjectSqlMaker.rb +74 -0
  49. data/lib/lafcadio/objectStore/ObjectStore.rb +207 -0
  50. data/lib/lafcadio/objectStore/ObjectStore.rb~ +207 -0
  51. data/lib/lafcadio/objectStore/SqlValueConverter.rb +30 -0
  52. data/lib/lafcadio/objectStore/SqlValueConverter.rb~ +30 -0
  53. data/lib/lafcadio/query.rb +203 -0
  54. data/lib/lafcadio/query/Compare.rb +55 -0
  55. data/lib/lafcadio/query/CompoundCondition.rb +39 -0
  56. data/lib/lafcadio/query/Condition.rb +66 -0
  57. data/lib/lafcadio/query/Condition.rb~ +66 -0
  58. data/lib/lafcadio/query/Equals.rb +45 -0
  59. data/lib/lafcadio/query/In.rb +20 -0
  60. data/lib/lafcadio/query/Like.rb +48 -0
  61. data/lib/lafcadio/query/Link.rb +20 -0
  62. data/lib/lafcadio/query/Max.rb +32 -0
  63. data/lib/lafcadio/query/Max.rb~ +25 -0
  64. data/lib/lafcadio/query/Not.rb +21 -0
  65. data/lib/lafcadio/query/Query.rb +92 -0
  66. data/lib/lafcadio/schema.rb +2 -0
  67. data/lib/lafcadio/schema/CreateTableStatement.rb +61 -0
  68. data/lib/lafcadio/schema/CreateTableStatement.rb~ +59 -0
  69. data/lib/lafcadio/test.rb +2 -0
  70. data/lib/lafcadio/test/LafcadioTestCase.rb +17 -0
  71. data/lib/lafcadio/test/testconfig.dat +13 -0
  72. data/lib/lafcadio/util.rb +180 -0
  73. data/lib/lafcadio/util/Context.rb +61 -0
  74. data/lib/lafcadio/util/ContextualService.rb +33 -0
  75. data/lib/lafcadio/util/English.rb +117 -0
  76. data/lib/lafcadio/util/HashOfArrays.rb +48 -0
  77. data/lib/lafcadio/util/LafcadioConfig.rb +25 -0
  78. data/lib/lafcadio/util/QueueHash.rb +67 -0
  79. data/lib/lafcadio/util/UsStates.rb +29 -0
  80. data/lib/lafcadio/xml.rb +2 -0
  81. metadata +135 -0
@@ -0,0 +1,371 @@
1
+ require 'lafcadio/objectField/LinkField'
2
+ require 'lafcadio/objectStore/DomainComparable'
3
+ require 'lafcadio/objectStore/DomainObjectProxy'
4
+
5
+ module Lafcadio
6
+ # All classes that correspond to a table in the database need to be children
7
+ # of DomainObject.
8
+ #
9
+ # = Defining fields
10
+ # There are two ways to define the fields of a DomainObject subclass.
11
+ # 1. Defining fields in an XML file. To do this,
12
+ # 1. Set one directory to contain all your XML files, by setting
13
+ # +classDefinitionDir+ in your LafcadioConfig file.
14
+ # 2. Write one XML file per domain class. For example, a User.xml file
15
+ # might look like:
16
+ # <lafcadio_class_definition name="User">
17
+ # <field name="lastName" class="TextField"/>
18
+ # <field name="email" class="TextField"/>
19
+ # <field name="password" class="TextField"/>
20
+ # <field name="birthday" class="DateField"/>
21
+ # </lafcadio_class_definition>
22
+ # 2. Overriding DomainObject.getClassFields. The method should return an Array
23
+ # of instances of ObjectField or its children. The order is unimportant.
24
+ # For example:
25
+ # class User < DomainObject
26
+ # def User.getClassFields
27
+ # fields = []
28
+ # fields << TextField.new(self, 'firstName')
29
+ # fields << TextField.new(self, 'lastName')
30
+ # fields << TextField.new(self, 'email')
31
+ # fields << TextField.new(self, 'password')
32
+ # fields << DateField.new(self, 'birthday')
33
+ # fields
34
+ # end
35
+ # end
36
+ #
37
+ # = Setting and retrieving fields
38
+ # Once your fields are defined, you can create an instance by passing in a
39
+ # hash of field names and values.
40
+ # john = User.new( 'firstName' => 'John', 'lastName' => 'Doe',
41
+ # 'email' => 'john.doe@email.com',
42
+ # 'password' => 'my_password',
43
+ # 'birthday' => tenYearsAgo )
44
+ #
45
+ # You can read and write these fields like normal instance attributes.
46
+ # john.email => 'john.doe@email.com'
47
+ # john.email = 'john.doe@mail.email.com'
48
+ #
49
+ # If your domain class has fields that refer to other domain classes, or even
50
+ # to another row in the same table, you can use a LinkField to express the
51
+ # relation.
52
+ # <lafcadio_class_definition name="Message">
53
+ # <field name="subject" class="TextField" />
54
+ # <field name="body" class="TextField" />
55
+ # <field name="author" class="LinkField" linkedType="User" />
56
+ # <field name="recipient" class="LinkField" linkedType="User" />
57
+ # <field name="dateSent" class="DateField" />
58
+ # </lafcadio_class_definition>
59
+ #
60
+ # msg = Message.new( 'subject' => 'hi there',
61
+ # 'body' => 'You wanna go to the movies on Saturday?',
62
+ # 'author' => john, 'recipient' => jane,
63
+ # 'dateSent' => Date.today )
64
+ #
65
+ # = pkId and committing
66
+ # Lafcadio requires that each table has a numeric primary key. It assumes that
67
+ # this key is named +pkId+ in the database, though that can be overridden.
68
+ #
69
+ # When you create a domain object by calling new, you should not assign a
70
+ # +pkId+ to the new instance. The pkId will automatically be set when you
71
+ # commit the object by calling DomainObject#commit.
72
+ #
73
+ # However, you may want to manually set +pkId+ when setting up a test case, so
74
+ # you can ensure that a domain object has a given primary key.
75
+ #
76
+ # = Naming assumptions, and how to override them
77
+ # By default, Lafcadio assumes that every domain object is indexed by the
78
+ # field +pkId+ in the database schema. If you're dealing with a table that
79
+ # uses a different field name, override DomainObject.sqlPrimaryKeyName.
80
+ # However, you will always use +pkId+ in your Ruby code.
81
+ #
82
+ # Lafcadio assumes that a domain class corresponds to a table whose name is
83
+ # the plural of the class name, and whose first letter is lowercase. A User
84
+ # class is assumed to be stored in a "users" table, while a ProductCategory
85
+ # class is assumed to be stored in a "productCategories" table. Override
86
+ # DomainObject.tableName to override this behavior.
87
+ #
88
+ # = Inheritance
89
+ # Domain classes can inherit from other domain classes; they have all the
90
+ # fields of any concrete superclasses plus any new fields defined for
91
+ # themselves. You can use normal inheritance to define this:
92
+ # class User < DomainObject
93
+ # ...
94
+ # end
95
+ #
96
+ # class Administrator < User
97
+ # ...
98
+ # end
99
+ #
100
+ # Lafcadio assumes that each concrete class has a corresponding table, and
101
+ # that each table has a +pkId+ field that is used to match rows between
102
+ # different levels.
103
+ class DomainObject
104
+ @@subclassHash = {}
105
+ @@classFields = {}
106
+
107
+ COMMIT_ADD = 1
108
+ COMMIT_EDIT = 2
109
+ COMMIT_DELETE = 3
110
+
111
+ include DomainComparable
112
+
113
+ def DomainObject.classFields #:nodoc:
114
+ classFields = @@classFields[self]
115
+ unless classFields
116
+ @@classFields[self] = self.getClassFields
117
+ classFields = @@classFields[self]
118
+ end
119
+ classFields
120
+ end
121
+
122
+ def DomainObject.abstractSubclasses #:nodoc:
123
+ require 'lafcadio/domain'
124
+ [ MapObject ]
125
+ end
126
+
127
+ def DomainObject.selfAndConcreteSuperclasses # :nodoc:
128
+ classes = [ ]
129
+ anObjectType = self
130
+ until(anObjectType == DomainObject ||
131
+ abstractSubclasses.index(anObjectType) != nil)
132
+ classes << anObjectType
133
+ anObjectType = anObjectType.superclass
134
+ end
135
+ classes
136
+ end
137
+
138
+ def DomainObject.method_missing(methodId) #:nodoc:
139
+ require 'lafcadio/domain'
140
+ ObjectType.getObjectType( self ).send( methodId.id2name )
141
+ end
142
+
143
+ def DomainObject.getClassField(fieldName) #:nodoc:
144
+ field = nil
145
+ self.classFields.each { |aField|
146
+ field = aField if aField.name == fieldName
147
+ }
148
+ field
149
+ end
150
+
151
+ def DomainObject.getField( fieldName ) #:nodoc:
152
+ aDomainClass = self
153
+ field = nil
154
+ while aDomainClass < DomainObject && !field
155
+ field = aDomainClass.getClassField( fieldName )
156
+ aDomainClass = aDomainClass.superclass
157
+ end
158
+ if field
159
+ field
160
+ else
161
+ errStr = "Couldn't find field \"#{ field }\" in " +
162
+ "#{ self } domain class"
163
+ raise( MissingError, errStr, caller )
164
+ end
165
+ end
166
+
167
+ def DomainObject.dependentClasses #:nodoc:
168
+ dependentClasses = {}
169
+ DomainObject.subclasses.each { |aClass|
170
+ if aClass != DomainObjectProxy &&
171
+ (!DomainObject.abstractSubclasses.index(aClass))
172
+ aClass.classFields.each { |field|
173
+ if field.class <= LinkField && field.linkedType == self.objectType
174
+ dependentClasses[aClass] = field
175
+ end
176
+ }
177
+ end
178
+ }
179
+ dependentClasses
180
+ end
181
+
182
+ def DomainObject.objectType #:nodoc:
183
+ self
184
+ end
185
+
186
+ # Returns an array of all fields defined for this class and all concrete
187
+ # superclasses.
188
+ def DomainObject.allFields
189
+ allFields = []
190
+ selfAndConcreteSuperclasses.each { |aClass|
191
+ aClass.classFields.each { |field| allFields << field }
192
+ }
193
+ allFields
194
+ end
195
+
196
+ def DomainObject.inherited(subclass) #:nodoc:
197
+ @@subclassHash[subclass] = true
198
+ end
199
+
200
+ def DomainObject.subclasses #:nodoc:
201
+ @@subclassHash.keys
202
+ end
203
+
204
+ def DomainObject.isConcrete? #:nodoc:
205
+ (self != DomainObject && abstractSubclasses.index(self).nil?)
206
+ end
207
+
208
+ def DomainObject.isBasedOn? #:nodoc:
209
+ self.superclass.isConcrete?
210
+ end
211
+
212
+ def self.getDomainDirs #:nodoc:
213
+ config = LafcadioConfig.new
214
+ classPath = config['classpath']
215
+ domainDirStr = config['domainDirs']
216
+ if domainDirStr
217
+ domainDirs = domainDirStr.split(',')
218
+ else
219
+ domainDirs = [ classPath + 'domain/' ]
220
+ end
221
+ end
222
+
223
+ def self.getObjectTypeFromString(typeString) #:nodoc:
224
+ require 'lafcadio/objectStore/CouldntMatchObjectTypeError'
225
+ objectType = nil
226
+ typeString =~ /([^\:]*)$/
227
+ fileName = $1
228
+ getDomainDirs.each { |domainDir|
229
+ if Dir.entries(domainDir).index("#{fileName}.rb")
230
+ require "#{ domainDir }#{ fileName }"
231
+ end
232
+ }
233
+ if (domainFilesStr = LafcadioConfig.new['domainFiles'])
234
+ domainFilesStr.split(',').each { |domainFile|
235
+ require domainFile
236
+ }
237
+ end
238
+ subclasses.each { |subclass|
239
+ objectType = subclass if subclass.to_s == typeString
240
+ }
241
+ if objectType
242
+ objectType
243
+ else
244
+ raise CouldntMatchObjectTypeError,
245
+ "couldn't match objectType #{typeString}", caller
246
+ end
247
+ end
248
+
249
+ attr_accessor :errorMessages, :pkId, :lastCommit, :fields, :fields_set
250
+ attr_reader :delete
251
+ protected :fields, :fields_set
252
+
253
+ # fieldHash should contain key-value associations for the different
254
+ # fields of this domain class. For example, instantiating a User class
255
+ # might look like:
256
+ #
257
+ # User.new( 'firstNames' => 'John', 'lastName' => 'Doe',
258
+ # 'email' => 'john.doe@email.com', 'password' => 'l33t' )
259
+ #
260
+ # In normal usage any code you write that creates a domain object will not
261
+ # define the +pkId+ field. The system assumes that a domain object with an
262
+ # undefined +pkId+ has yet to be inserted into the database, and when you
263
+ # commit the domain object a +pkId+ will automatically be assigned.
264
+ #
265
+ # If you're creating mock objects for unit tests, you can explicitly set
266
+ # the +pkId+ to represent objects that already exist in the database.
267
+ def initialize(fieldHash)
268
+ @fieldHash = fieldHash
269
+ @pkId = fieldHash['pkId']
270
+ @pkId = @pkId.to_i unless @pkId.nil?
271
+ @errorMessages = []
272
+ @fields = {}
273
+ @fields_set = []
274
+ end
275
+
276
+ def method_missing( methId, *args ) #:nodoc:
277
+ if ( field = get_setter_field( methId ) )
278
+ set_field( field, args.first )
279
+ elsif ( field = get_getter_field( methId ) )
280
+ get_field( field )
281
+ else
282
+ super( methId, *args )
283
+ end
284
+ end
285
+
286
+ def get_getter_field( methId ) #:nodoc:
287
+ begin
288
+ self.class.getField( methId.id2name )
289
+ rescue MissingError
290
+ nil
291
+ end
292
+ end
293
+
294
+ def get_setter_field( methId ) #:nodoc:
295
+ if methId.id2name =~ /(.*)=$/
296
+ begin
297
+ self.class.getField( $1 )
298
+ rescue MissingError
299
+ nil
300
+ end
301
+ else
302
+ nil
303
+ end
304
+ end
305
+
306
+ def get_field( field ) #:nodoc:
307
+ unless @fields_set.include?( field )
308
+ set_field( field, @fieldHash[field.name] )
309
+ end
310
+ @fields[field.name]
311
+ end
312
+
313
+ def set_field( field, value ) #:nodoc:
314
+ if field.class <= LinkField
315
+ if value.class != DomainObjectProxy && value
316
+ value = DomainObjectProxy.new(value)
317
+ end
318
+ end
319
+ @fields[field.name] = value
320
+ @fields_set << field
321
+ end
322
+
323
+ # Returns the subclass of DomainObject that this instance represents.
324
+ # Because of the way that proxying works, clients should call this method
325
+ # instead of Object.class.
326
+ def objectType
327
+ self.class.objectType
328
+ end
329
+
330
+ # This template method is called before every commit. Subclasses can
331
+ # override it to ensure code is executed before a commit.
332
+ def preCommitTrigger
333
+ nil
334
+ end
335
+
336
+ # This template method is called after every commit. Subclasses can
337
+ # override it to ensure code is executed after a commit.
338
+ def postCommitTrigger
339
+ nil
340
+ end
341
+
342
+ # Set the delete value to true if you want this domain object to be deleted
343
+ # from the database during its next commit.
344
+ def delete=(value)
345
+ if value && !pkId
346
+ raise "No point deleting an object that's not already in the DB"
347
+ end
348
+ @delete = value
349
+ end
350
+
351
+ # By default, to_s is considered an invalid operation for domain objects,
352
+ # and will raise an error. This behavior can be overridden by subclasses.
353
+ def to_s
354
+ raise "Don't make me into a string unless the type asks"
355
+ end
356
+
357
+ # Returns a clone, with all of the fields copied.
358
+ def clone
359
+ copy = super
360
+ copy.fields = @fields.clone
361
+ copy.fields_set = @fields_set.clone
362
+ copy
363
+ end
364
+
365
+ # Commits this domain object to the database.
366
+ def commit
367
+ require 'lafcadio/objectStore/ObjectStore'
368
+ ObjectStore.getObjectStore.commit self
369
+ end
370
+ end
371
+ end
@@ -0,0 +1,22 @@
1
+ require 'lafcadio/domain/DomainObject'
2
+
3
+ module Lafcadio
4
+ # Any domain class that is used mostly to map between two other domain
5
+ # classes should be a subclass of MapObject. Subclasses of MapObject should
6
+ # override MapObject.mappedTypes, returning a two-element array containing
7
+ # the domain classes that the map object maps between.
8
+ class MapObject < DomainObject
9
+ def MapObject.otherMappedType(firstType) #:nodoc:
10
+ types = mappedTypes
11
+ if types.index(firstType) == 0
12
+ types[1]
13
+ else
14
+ types[0]
15
+ end
16
+ end
17
+
18
+ def MapObject.subsidiaryMap #:nodoc:
19
+ nil
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,80 @@
1
+ require 'lafcadio/domain'
2
+ require 'lafcadio/util'
3
+
4
+ module Lafcadio
5
+ # A utility class that handles a few details for the DomainObject class. All
6
+ # the methods here are usually called as methods of DomainObject, and then
7
+ # delegated to this class.
8
+ class ObjectType
9
+ @@instances = {}
10
+
11
+ def self.flush #:nodoc:
12
+ @@instances = {}
13
+ end
14
+
15
+ def self.getObjectType( aClass ) #:nodoc:
16
+ instance = @@instances[aClass]
17
+ if instance.nil?
18
+ @@instances[aClass] = new( aClass )
19
+ instance = @@instances[aClass]
20
+ end
21
+ instance
22
+ end
23
+
24
+ private_class_method :new
25
+
26
+ def initialize(objectType) #:nodoc:
27
+ @objectType = objectType
28
+ ( @classFields, @xmlParser ) = [ nil, nil ]
29
+ dirName = LafcadioConfig.new['classDefinitionDir']
30
+ xmlFileName = @objectType.bareName + '.xml'
31
+ xmlPath = File.join( dirName, xmlFileName )
32
+ xml = ''
33
+ begin
34
+ File.open( xmlPath ) { |file| xml = file.readlines.join }
35
+ @xmlParser = ClassDefinitionXmlParser.new( @objectType, xml )
36
+ rescue Errno::ENOENT
37
+ # no xml file, so no @xmlParser
38
+ end
39
+ end
40
+
41
+ # Returns an Array of ObjectField instances for this domain class, parsing
42
+ # them from XML if necessary.
43
+ def getClassFields
44
+ unless @classFields
45
+ if @xmlParser
46
+ @classFields = @xmlParser.getClassFields
47
+ else
48
+ error_msg = "Couldn't find either an XML class description file " +
49
+ "or getClassFields method for " + @objectType.name
50
+ raise MissingError, error_msg, caller
51
+ end
52
+ end
53
+ @classFields
54
+ end
55
+
56
+ # Returns the name of the primary key in the database, retrieving it from
57
+ # the class definition XML if necessary.
58
+ def sqlPrimaryKeyName
59
+ if !@xmlParser.nil? && ( spkn = @xmlParser.sqlPrimaryKeyName )
60
+ spkn
61
+ else
62
+ 'pkId'
63
+ end
64
+ end
65
+
66
+ # Returns the table name, which is assumed to be the domain class name
67
+ # pluralized, and with the first letter lowercase. A User class is
68
+ # assumed to be stored in a "users" table, while a ProductCategory class is
69
+ # assumed to be stored in a "productCategories" table.
70
+ def tableName
71
+ if (!@xmlParser.nil? && tableName = @xmlParser.tableName)
72
+ tableName
73
+ else
74
+ tableName = @objectType.bareName
75
+ tableName[0] = tableName[0..0].downcase
76
+ English.plural tableName
77
+ end
78
+ end
79
+ end
80
+ end