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
data/lib/lafcadio.rb ADDED
@@ -0,0 +1,32 @@
1
+ # Lafcadio is an object-relational mapping library for Ruby and MySQL. Its
2
+ # design has a few aspects in mind:
3
+ # * The importance of unit-testing. Lafcadio includes a MockObjectStore which
4
+ # can take the place of the ObjectStore for unit tests, so you can test
5
+ # complex database-driven logic. Committing domain objects, running queries,
6
+ # and even triggers can all be written in the Lafcadio level, meaning that
7
+ # they can all be tested without hitting a live database.
8
+ # * Dealing with databases in the wild. Lafcadio excels at grappling with
9
+ # pre-existing database schemas and all the odd ways the people use databases
10
+ # in the wild. It requires very little from your schema, except for the fact
11
+ # that each table needs a single numeric primary key. It makes many
12
+ # assumptions about your naming conventions, but these assumptions can all be
13
+ # overridden.
14
+ #
15
+ # First-time users are recommended to read the tutorial at
16
+ # http://rubyforge.lafcadio.org/tutorial.html.
17
+
18
+ module Lafcadio
19
+ Version = "0.4.3"
20
+
21
+ require 'lafcadio/dateTime'
22
+ require 'lafcadio/domain'
23
+ require 'lafcadio/includer'
24
+ require 'lafcadio/mock'
25
+ require 'lafcadio/objectField'
26
+ require 'lafcadio/objectStore'
27
+ require 'lafcadio/query'
28
+ require 'lafcadio/schema'
29
+ require 'lafcadio/test'
30
+ require 'lafcadio/util'
31
+ require 'lafcadio/xml'
32
+ end
data/lib/lafcadio.rb~ ADDED
@@ -0,0 +1,32 @@
1
+ # Lafcadio is an object-relational mapping library for Ruby and MySQL. Its
2
+ # design has a few aspects in mind:
3
+ # * The importance of unit-testing. Lafcadio includes a MockObjectStore which
4
+ # can take the place of the ObjectStore for unit tests, so you can test
5
+ # complex database-driven logic. Committing domain objects, running queries,
6
+ # and even triggers can all be written in the Lafcadio level, meaning that
7
+ # they can all be tested without hitting a live database.
8
+ # * Dealing with databases in the wild. Lafcadio excels at grappling with
9
+ # pre-existing database schemas and all the odd ways the people use databases
10
+ # in the wild. It requires very little from your schema, except for the fact
11
+ # that each table needs a single numeric primary key. It makes many
12
+ # assumptions about your naming conventions, but these assumptions can all be
13
+ # overridden.
14
+ #
15
+ # First-time users are recommended to read the tutorial at
16
+ # http://rubyforge.lafcadio.org/tutorial.html.
17
+
18
+ module Lafcadio
19
+ Version = "0.4.2"
20
+
21
+ require 'lafcadio/dateTime'
22
+ require 'lafcadio/domain'
23
+ require 'lafcadio/includer'
24
+ require 'lafcadio/mock'
25
+ require 'lafcadio/objectField'
26
+ require 'lafcadio/objectStore'
27
+ require 'lafcadio/query'
28
+ require 'lafcadio/schema'
29
+ require 'lafcadio/test'
30
+ require 'lafcadio/util'
31
+ require 'lafcadio/xml'
32
+ end
@@ -0,0 +1,16 @@
1
+ dir = Dir.new 'lafcadio/'
2
+ dir.each { |entry|
3
+ if ![ '.', '..', 'CVS' ].index(entry) && entry !~ /~$/
4
+ begin
5
+ subDirName = entry
6
+ subDir = Dir.new "lafcadio/#{ subDirName }"
7
+ subDir.each { |entry|
8
+ if entry =~ /.rb$/
9
+ require "lafcadio/#{subDirName}/#{entry}"
10
+ end
11
+ }
12
+ rescue StandardError
13
+ # not a directory, whatev
14
+ end
15
+ end
16
+ }
@@ -0,0 +1,2 @@
1
+ require 'lafcadio/includer'
2
+ Includer.include( 'dateTime' )
@@ -0,0 +1,93 @@
1
+ module Lafcadio
2
+ # Represents a specific month in time. With the exception of Month.monthNames
3
+ # (which returns a zero-based array), every usage of the month value assumes
4
+ # that 1 equals January and 12 equals December.
5
+ class Month
6
+ # Returns an array of the full names of months (in English). Note that
7
+ # "January" is the 0th element, and "December" is the 11th element.
8
+ def Month.monthNames
9
+ [ "January", "February", "March", "April", "May", "June", "July",
10
+ "August", "September", "October", "November", "December" ]
11
+ end
12
+
13
+ include Comparable
14
+
15
+ attr_reader :month, :year
16
+
17
+ # A new month can be set to a specific +month+ and +year+, or you can call
18
+ # Month.new with no arguments to receive the current month.
19
+ def initialize( month = nil, year = nil )
20
+ require 'date'
21
+ if month.nil? || year.nil?
22
+ date = Date.today
23
+ month = date.mon unless month
24
+ year = date.year unless year
25
+ end
26
+ fail "invalid month" if month < 1 || month > 12
27
+ @month = month
28
+ @year = year
29
+ end
30
+
31
+ # Compare this Month to another Month.
32
+ def <=>(anOther)
33
+ if @year == anOther.year
34
+ @month <=> anOther.month
35
+ else
36
+ @year <=> anOther.year
37
+ end
38
+ end
39
+
40
+ # Returns a string of the format "January 2001".
41
+ def to_s
42
+ Month.monthNames[@month-1][0..2] + " " + @year.to_s
43
+ end
44
+
45
+ # Calculate a hash value for this Month.
46
+ def hash
47
+ "#{@year}#{@month}".to_i
48
+ end
49
+
50
+ # Is this Month equal to +anOther+? +anOther+ must be another Month of the
51
+ # same value.
52
+ def eql?(anOther)
53
+ self == anOther
54
+ end
55
+
56
+ # Returns a new Month that is +amountToAdd+ months later.
57
+ def +( amountToAdd )
58
+ ( fullYears, remainingMonths ) = amountToAdd.divmod( 12 )
59
+ resultYear = @year + fullYears
60
+ resultMonth = @month + remainingMonths
61
+ if resultMonth > 12
62
+ resultMonth -= 12
63
+ resultYear += 1
64
+ end
65
+ Month.new( resultMonth, resultYear )
66
+ end
67
+
68
+ # Returns a new Month that is +amountToSubtract+ months earlier.
69
+ def -(amountToSubtract)
70
+ self + (-amountToSubtract)
71
+ end
72
+
73
+ # Returns the previous Month.
74
+ def prev
75
+ self - 1
76
+ end
77
+
78
+ # Returns the next Month.
79
+ def next
80
+ self + 1
81
+ end
82
+
83
+ # Returns the first Date of the month.
84
+ def startDate
85
+ Date.new( @year, @month, 1 )
86
+ end
87
+
88
+ # Returns the last Date of the month.
89
+ def endDate
90
+ self.next.startDate - 1
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,119 @@
1
+ require 'lafcadio/includer'
2
+ Includer.include( 'domain' )
3
+ require 'lafcadio/objectField'
4
+ require 'lafcadio/util'
5
+ require 'rexml/document'
6
+
7
+ module Lafcadio
8
+ class ClassDefinitionXmlParser # :nodoc: all
9
+ class InvalidDataError < ArgumentError; end
10
+
11
+ class FieldAttribute
12
+ INTEGER = 1
13
+ BOOLEAN = 2
14
+ ENUM = 3
15
+ HASH = 4
16
+
17
+ attr_reader :name, :valueClass
18
+
19
+ def initialize( name, valueClass, objectFieldClass = nil )
20
+ @name = name; @valueClass = valueClass
21
+ @objectFieldClass = objectFieldClass
22
+ end
23
+
24
+ def valueFromString( valueStr )
25
+ if @valueClass == INTEGER
26
+ valueStr.to_i
27
+ elsif @valueClass == BOOLEAN
28
+ valueStr == 'y'
29
+ elsif @valueClass == ENUM
30
+ eval "#{ @objectFieldClass.name }::#{ valueStr }"
31
+ end
32
+ end
33
+
34
+ def valueFromElt( elt )
35
+ hash = {}
36
+ elt.elements.each( English.singular( @name ) ) { |subElt|
37
+ key = subElt.attributes['key'] == 'true'
38
+ value = subElt.text.to_s
39
+ hash[key] = value
40
+ }
41
+ hash
42
+ end
43
+
44
+ def maybeSetFieldAttr( field, fieldElt )
45
+ setterMethod = "#{ name }="
46
+ if field.respond_to?( setterMethod )
47
+ if valueClass != FieldAttribute::HASH
48
+ if ( attrStr = fieldElt.attributes[name] )
49
+ field.send( setterMethod, valueFromString( attrStr ) )
50
+ end
51
+ else
52
+ if ( attrElt = fieldElt.elements[name] )
53
+ field.send( setterMethod, valueFromElt( attrElt ) )
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+
60
+ def initialize( domainClass, xml )
61
+ @domainClass = domainClass
62
+ @xmlDocRoot = REXML::Document.new( xml ).root
63
+ @namesProcessed = {}
64
+ end
65
+
66
+ def get_class_field( fieldElt )
67
+ className = fieldElt.attributes['class'].to_s
68
+ name = fieldElt.attributes['name']
69
+ begin
70
+ fieldClass = Class.getClass( 'Lafcadio::' + className )
71
+ register_name( name )
72
+ field = fieldClass.instantiateFromXml( @domainClass, fieldElt )
73
+ possibleFieldAttributes.each { |fieldAttr|
74
+ fieldAttr.maybeSetFieldAttr( field, fieldElt )
75
+ }
76
+ rescue MissingError
77
+ msg = "Couldn't find field class '#{ className }' for field " +
78
+ "'#{ name }'"
79
+ raise( MissingError, msg, caller )
80
+ end
81
+ field
82
+ end
83
+
84
+ def getClassFields
85
+ namesProcessed = {}
86
+ fields = []
87
+ @xmlDocRoot.elements.each('field') { |fieldElt|
88
+ fields << get_class_field( fieldElt )
89
+ }
90
+ fields
91
+ end
92
+
93
+ def register_name( name )
94
+ raise InvalidDataError if @namesProcessed[name]
95
+ @namesProcessed[name] = true
96
+ end
97
+
98
+ def sqlPrimaryKeyName
99
+ @xmlDocRoot.attributes['sqlPrimaryKeyName']
100
+ end
101
+
102
+ def tableName
103
+ @xmlDocRoot.attributes['tableName']
104
+ end
105
+
106
+ def possibleFieldAttributes
107
+ fieldAttr = []
108
+ fieldAttr << FieldAttribute.new( 'size', FieldAttribute::INTEGER )
109
+ fieldAttr << FieldAttribute.new( 'unique', FieldAttribute::BOOLEAN )
110
+ fieldAttr << FieldAttribute.new( 'notNull', FieldAttribute::BOOLEAN )
111
+ fieldAttr << FieldAttribute.new( 'enumType', FieldAttribute::ENUM,
112
+ BooleanField )
113
+ fieldAttr << FieldAttribute.new( 'enums', FieldAttribute::HASH )
114
+ fieldAttr << FieldAttribute.new( 'range', FieldAttribute::ENUM,
115
+ DateField )
116
+ fieldAttr << FieldAttribute.new( 'large', FieldAttribute::BOOLEAN )
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,119 @@
1
+ require 'lafcadio/includer'
2
+ Includer.include( 'domain' )
3
+ require 'lafcadio/objectField'
4
+ require 'lafcadio/util'
5
+ require 'rexml/document'
6
+
7
+ module Lafcadio
8
+ class ClassDefinitionXmlParser # :nodoc: all
9
+ class InvalidDataError < ArgumentError; end
10
+
11
+ class FieldAttribute
12
+ INTEGER = 1
13
+ BOOLEAN = 2
14
+ ENUM = 3
15
+ HASH = 4
16
+
17
+ attr_reader :name, :valueClass
18
+
19
+ def initialize( name, valueClass, objectFieldClass = nil )
20
+ @name = name; @valueClass = valueClass
21
+ @objectFieldClass = objectFieldClass
22
+ end
23
+
24
+ def valueFromString( valueStr )
25
+ if @valueClass == INTEGER
26
+ valueStr.to_i
27
+ elsif @valueClass == BOOLEAN
28
+ valueStr == 'y'
29
+ elsif @valueClass == ENUM
30
+ eval "#{ @objectFieldClass.name }::#{ valueStr }"
31
+ end
32
+ end
33
+
34
+ def valueFromElt( elt )
35
+ hash = {}
36
+ elt.elements.each( English.singular( @name ) ) { |subElt|
37
+ key = subElt.attributes['key'] == 'true'
38
+ value = subElt.text.to_s
39
+ hash[key] = value
40
+ }
41
+ hash
42
+ end
43
+
44
+ def maybeSetFieldAttr( field, fieldElt )
45
+ setterMethod = "#{ name }="
46
+ if field.respond_to?( setterMethod )
47
+ if valueClass != FieldAttribute::HASH
48
+ if ( attrStr = fieldElt.attributes[name] )
49
+ field.send( setterMethod, valueFromString( attrStr ) )
50
+ end
51
+ else
52
+ if ( attrElt = fieldElt.elements[name] )
53
+ field.send( setterMethod, valueFromElt( attrElt ) )
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+
60
+ def initialize( domainClass, xml )
61
+ @domainClass = domainClass
62
+ @xmlDocRoot = REXML::Document.new( xml ).root
63
+ @namesProcessed = {}
64
+ end
65
+
66
+ def get_class_field( fieldElt )
67
+ className = fieldElt.attributes['class'].to_s
68
+ name = fieldElt.attributes['name']
69
+ begin
70
+ fieldClass = Class.getClass( 'Lafcadio::' + className )
71
+ register_name( name )
72
+ field = fieldClass.instantiateFromXml( @domainClass, fieldElt )
73
+ possibleFieldAttributes.each { |fieldAttr|
74
+ fieldAttr.maybeSetFieldAttr( field, fieldElt )
75
+ }
76
+ rescue MissingError
77
+ msg = "Couldn't find field class '#{ className }' for field " +
78
+ "'#{ name }'"
79
+ raise( MissingError, msg, caller )
80
+ end
81
+ field
82
+ end
83
+
84
+ def getClassFields
85
+ namesProcessed = {}
86
+ fields = []
87
+ @xmlDocRoot.elements.each('field') { |fieldElt|
88
+ fields << get_class_field( fieldElt )
89
+ }
90
+ fields
91
+ end
92
+
93
+ def register_name( name )
94
+ raise InvalidDataError if @namesProcessed[name]
95
+ @namesProcessed[name] = true
96
+ end
97
+
98
+ def sqlPrimaryKeyName
99
+ @xmlDocRoot.attributes['sqlPrimaryKeyName']
100
+ end
101
+
102
+ def tableName
103
+ @xmlDocRoot.attributes['tableName']
104
+ end
105
+
106
+ def possibleFieldAttributes
107
+ fieldAttr = []
108
+ fieldAttr << FieldAttribute.new( 'size', FieldAttribute::INTEGER )
109
+ fieldAttr << FieldAttribute.new( 'unique', FieldAttribute::BOOLEAN )
110
+ fieldAttr << FieldAttribute.new( 'notNull', FieldAttribute::BOOLEAN )
111
+ fieldAttr << FieldAttribute.new( 'enumType', FieldAttribute::ENUM,
112
+ BooleanField )
113
+ fieldAttr << FieldAttribute.new( 'enums', FieldAttribute::HASH )
114
+ fieldAttr << FieldAttribute.new( 'range', FieldAttribute::ENUM,
115
+ DateField )
116
+ fieldAttr << FieldAttribute.new( 'large', FieldAttribute::BOOLEAN )
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,375 @@
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.getClassFieldByDbFieldName( fieldName ) #:nodoc:
152
+ self.classFields.find { |field| field.dbFieldName == fieldName }
153
+ end
154
+
155
+ def DomainObject.getField( fieldName ) #:nodoc:
156
+ aDomainClass = self
157
+ field = nil
158
+ while aDomainClass < DomainObject && !field
159
+ field = aDomainClass.getClassField( fieldName )
160
+ aDomainClass = aDomainClass.superclass
161
+ end
162
+ if field
163
+ field
164
+ else
165
+ errStr = "Couldn't find field \"#{ field }\" in " +
166
+ "#{ self } domain class"
167
+ raise( MissingError, errStr, caller )
168
+ end
169
+ end
170
+
171
+ def DomainObject.dependentClasses #:nodoc:
172
+ dependentClasses = {}
173
+ DomainObject.subclasses.each { |aClass|
174
+ if aClass != DomainObjectProxy &&
175
+ (!DomainObject.abstractSubclasses.index(aClass))
176
+ aClass.classFields.each { |field|
177
+ if field.class <= LinkField && field.linkedType == self.objectType
178
+ dependentClasses[aClass] = field
179
+ end
180
+ }
181
+ end
182
+ }
183
+ dependentClasses
184
+ end
185
+
186
+ def DomainObject.objectType #:nodoc:
187
+ self
188
+ end
189
+
190
+ # Returns an array of all fields defined for this class and all concrete
191
+ # superclasses.
192
+ def DomainObject.allFields
193
+ allFields = []
194
+ selfAndConcreteSuperclasses.each { |aClass|
195
+ aClass.classFields.each { |field| allFields << field }
196
+ }
197
+ allFields
198
+ end
199
+
200
+ def DomainObject.inherited(subclass) #:nodoc:
201
+ @@subclassHash[subclass] = true
202
+ end
203
+
204
+ def DomainObject.subclasses #:nodoc:
205
+ @@subclassHash.keys
206
+ end
207
+
208
+ def DomainObject.isConcrete? #:nodoc:
209
+ (self != DomainObject && abstractSubclasses.index(self).nil?)
210
+ end
211
+
212
+ def DomainObject.isBasedOn? #:nodoc:
213
+ self.superclass.isConcrete?
214
+ end
215
+
216
+ def self.getDomainDirs #:nodoc:
217
+ config = LafcadioConfig.new
218
+ classPath = config['classpath']
219
+ domainDirStr = config['domainDirs']
220
+ if domainDirStr
221
+ domainDirs = domainDirStr.split(',')
222
+ else
223
+ domainDirs = [ classPath + 'domain/' ]
224
+ end
225
+ end
226
+
227
+ def self.getObjectTypeFromString(typeString) #:nodoc:
228
+ require 'lafcadio/objectStore/CouldntMatchObjectTypeError'
229
+ objectType = nil
230
+ typeString =~ /([^\:]*)$/
231
+ fileName = $1
232
+ getDomainDirs.each { |domainDir|
233
+ if Dir.entries(domainDir).index("#{fileName}.rb")
234
+ require "#{ domainDir }#{ fileName }"
235
+ end
236
+ }
237
+ if (domainFilesStr = LafcadioConfig.new['domainFiles'])
238
+ domainFilesStr.split(',').each { |domainFile|
239
+ require domainFile
240
+ }
241
+ end
242
+ subclasses.each { |subclass|
243
+ objectType = subclass if subclass.to_s == typeString
244
+ }
245
+ if objectType
246
+ objectType
247
+ else
248
+ raise CouldntMatchObjectTypeError,
249
+ "couldn't match objectType #{typeString}", caller
250
+ end
251
+ end
252
+
253
+ attr_accessor :errorMessages, :pkId, :lastCommit, :fields, :fields_set
254
+ attr_reader :delete
255
+ protected :fields, :fields_set
256
+
257
+ # fieldHash should contain key-value associations for the different
258
+ # fields of this domain class. For example, instantiating a User class
259
+ # might look like:
260
+ #
261
+ # User.new( 'firstNames' => 'John', 'lastName' => 'Doe',
262
+ # 'email' => 'john.doe@email.com', 'password' => 'l33t' )
263
+ #
264
+ # In normal usage any code you write that creates a domain object will not
265
+ # define the +pkId+ field. The system assumes that a domain object with an
266
+ # undefined +pkId+ has yet to be inserted into the database, and when you
267
+ # commit the domain object a +pkId+ will automatically be assigned.
268
+ #
269
+ # If you're creating mock objects for unit tests, you can explicitly set
270
+ # the +pkId+ to represent objects that already exist in the database.
271
+ def initialize(fieldHash)
272
+ @fieldHash = fieldHash
273
+ @pkId = fieldHash['pkId']
274
+ @pkId = @pkId.to_i unless @pkId.nil?
275
+ @errorMessages = []
276
+ @fields = {}
277
+ @fields_set = []
278
+ end
279
+
280
+ def method_missing( methId, *args ) #:nodoc:
281
+ if ( field = get_setter_field( methId ) )
282
+ set_field( field, args.first )
283
+ elsif ( field = get_getter_field( methId ) )
284
+ get_field( field )
285
+ else
286
+ super( methId, *args )
287
+ end
288
+ end
289
+
290
+ def get_getter_field( methId ) #:nodoc:
291
+ begin
292
+ self.class.getField( methId.id2name )
293
+ rescue MissingError
294
+ nil
295
+ end
296
+ end
297
+
298
+ def get_setter_field( methId ) #:nodoc:
299
+ if methId.id2name =~ /(.*)=$/
300
+ begin
301
+ self.class.getField( $1 )
302
+ rescue MissingError
303
+ nil
304
+ end
305
+ else
306
+ nil
307
+ end
308
+ end
309
+
310
+ def get_field( field ) #:nodoc:
311
+ unless @fields_set.include?( field )
312
+ set_field( field, @fieldHash[field.name] )
313
+ end
314
+ @fields[field.name]
315
+ end
316
+
317
+ def set_field( field, value ) #:nodoc:
318
+ if field.class <= LinkField
319
+ if value.class != DomainObjectProxy && value
320
+ value = DomainObjectProxy.new(value)
321
+ end
322
+ end
323
+ @fields[field.name] = value
324
+ @fields_set << field
325
+ end
326
+
327
+ # Returns the subclass of DomainObject that this instance represents.
328
+ # Because of the way that proxying works, clients should call this method
329
+ # instead of Object.class.
330
+ def objectType
331
+ self.class.objectType
332
+ end
333
+
334
+ # This template method is called before every commit. Subclasses can
335
+ # override it to ensure code is executed before a commit.
336
+ def preCommitTrigger
337
+ nil
338
+ end
339
+
340
+ # This template method is called after every commit. Subclasses can
341
+ # override it to ensure code is executed after a commit.
342
+ def postCommitTrigger
343
+ nil
344
+ end
345
+
346
+ # Set the delete value to true if you want this domain object to be deleted
347
+ # from the database during its next commit.
348
+ def delete=(value)
349
+ if value && !pkId
350
+ raise "No point deleting an object that's not already in the DB"
351
+ end
352
+ @delete = value
353
+ end
354
+
355
+ # By default, to_s is considered an invalid operation for domain objects,
356
+ # and will raise an error. This behavior can be overridden by subclasses.
357
+ def to_s
358
+ raise "Don't make me into a string unless the type asks"
359
+ end
360
+
361
+ # Returns a clone, with all of the fields copied.
362
+ def clone
363
+ copy = super
364
+ copy.fields = @fields.clone
365
+ copy.fields_set = @fields_set.clone
366
+ copy
367
+ end
368
+
369
+ # Commits this domain object to the database.
370
+ def commit
371
+ require 'lafcadio/objectStore/ObjectStore'
372
+ ObjectStore.getObjectStore.commit self
373
+ end
374
+ end
375
+ end