lafcadio 0.4.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.
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