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
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