lafcadio 0.4.3 → 0.5.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (88) hide show
  1. data/bin/lafcadio_schema +28 -0
  2. data/lib/lafcadio.rb +3 -4
  3. data/lib/lafcadio.rb~ +3 -4
  4. data/lib/lafcadio/TestSuite.rb +2 -0
  5. data/lib/lafcadio/TestSuite.rb~ +16 -0
  6. data/lib/lafcadio/dateTime.rb +93 -2
  7. data/lib/lafcadio/{dateTime/Month.rb → dateTime.rb~} +33 -33
  8. data/lib/lafcadio/depend.rb +3 -0
  9. data/lib/lafcadio/domain.rb +574 -70
  10. data/lib/lafcadio/domain.rb~ +570 -70
  11. data/lib/lafcadio/mock.rb +92 -2
  12. data/lib/lafcadio/mock.rb~ +93 -0
  13. data/lib/lafcadio/objectField.rb +614 -3
  14. data/lib/lafcadio/objectField.rb~ +618 -0
  15. data/lib/lafcadio/objectStore.rb +662 -19
  16. data/lib/lafcadio/objectStore.rb~ +746 -0
  17. data/lib/lafcadio/query.rb +415 -31
  18. data/lib/lafcadio/query.rb~ +572 -0
  19. data/lib/lafcadio/schema.rb +57 -2
  20. data/lib/lafcadio/test.rb +17 -2
  21. data/lib/lafcadio/{test/LafcadioTestCase.rb → test.rb~} +5 -5
  22. data/lib/lafcadio/test/testconfig.dat +1 -1
  23. data/lib/lafcadio/util.rb +337 -20
  24. metadata +16 -77
  25. data/lib/lafcadio/domain/DomainObject.rb +0 -375
  26. data/lib/lafcadio/domain/DomainObject.rb~ +0 -371
  27. data/lib/lafcadio/domain/MapObject.rb +0 -22
  28. data/lib/lafcadio/domain/ObjectType.rb +0 -80
  29. data/lib/lafcadio/includer.rb +0 -18
  30. data/lib/lafcadio/mock/MockDbBridge.rb +0 -78
  31. data/lib/lafcadio/mock/MockDbBridge.rb~ +0 -74
  32. data/lib/lafcadio/mock/MockObjectStore.rb +0 -20
  33. data/lib/lafcadio/objectField/AutoIncrementField.rb +0 -25
  34. data/lib/lafcadio/objectField/BooleanField.rb +0 -83
  35. data/lib/lafcadio/objectField/DateField.rb +0 -33
  36. data/lib/lafcadio/objectField/DateTimeField.rb +0 -25
  37. data/lib/lafcadio/objectField/DecimalField.rb +0 -41
  38. data/lib/lafcadio/objectField/EmailField.rb +0 -28
  39. data/lib/lafcadio/objectField/EnumField.rb +0 -62
  40. data/lib/lafcadio/objectField/FieldValueError.rb +0 -4
  41. data/lib/lafcadio/objectField/IntegerField.rb +0 -15
  42. data/lib/lafcadio/objectField/LinkField.rb +0 -92
  43. data/lib/lafcadio/objectField/LinkField.rb~ +0 -86
  44. data/lib/lafcadio/objectField/MoneyField.rb +0 -13
  45. data/lib/lafcadio/objectField/MonthField.rb +0 -16
  46. data/lib/lafcadio/objectField/ObjectField.rb +0 -142
  47. data/lib/lafcadio/objectField/PasswordField.rb +0 -29
  48. data/lib/lafcadio/objectField/StateField.rb +0 -13
  49. data/lib/lafcadio/objectField/SubsetLinkField.rb +0 -25
  50. data/lib/lafcadio/objectField/TextField.rb +0 -23
  51. data/lib/lafcadio/objectField/TextListField.rb +0 -21
  52. data/lib/lafcadio/objectField/TimeStampField.rb +0 -15
  53. data/lib/lafcadio/objectStore/Cache.rb +0 -81
  54. data/lib/lafcadio/objectStore/Committer.rb +0 -65
  55. data/lib/lafcadio/objectStore/CouldntMatchObjectTypeError.rb +0 -4
  56. data/lib/lafcadio/objectStore/DbBridge.rb +0 -140
  57. data/lib/lafcadio/objectStore/DbBridge.rb~ +0 -140
  58. data/lib/lafcadio/objectStore/DomainComparable.rb +0 -25
  59. data/lib/lafcadio/objectStore/DomainObjectInitError.rb +0 -9
  60. data/lib/lafcadio/objectStore/DomainObjectNotFoundError.rb +0 -4
  61. data/lib/lafcadio/objectStore/DomainObjectProxy.rb +0 -62
  62. data/lib/lafcadio/objectStore/DomainObjectSqlMaker.rb +0 -74
  63. data/lib/lafcadio/objectStore/ObjectStore.rb +0 -207
  64. data/lib/lafcadio/objectStore/ObjectStore.rb~ +0 -207
  65. data/lib/lafcadio/objectStore/SqlValueConverter.rb +0 -30
  66. data/lib/lafcadio/objectStore/SqlValueConverter.rb~ +0 -30
  67. data/lib/lafcadio/query/Compare.rb +0 -55
  68. data/lib/lafcadio/query/CompoundCondition.rb +0 -39
  69. data/lib/lafcadio/query/Condition.rb +0 -66
  70. data/lib/lafcadio/query/Condition.rb~ +0 -66
  71. data/lib/lafcadio/query/Equals.rb +0 -45
  72. data/lib/lafcadio/query/In.rb +0 -20
  73. data/lib/lafcadio/query/Like.rb +0 -48
  74. data/lib/lafcadio/query/Link.rb +0 -20
  75. data/lib/lafcadio/query/Max.rb +0 -32
  76. data/lib/lafcadio/query/Max.rb~ +0 -25
  77. data/lib/lafcadio/query/Not.rb +0 -21
  78. data/lib/lafcadio/query/Query.rb +0 -92
  79. data/lib/lafcadio/schema/CreateTableStatement.rb +0 -61
  80. data/lib/lafcadio/schema/CreateTableStatement.rb~ +0 -59
  81. data/lib/lafcadio/util/Context.rb +0 -61
  82. data/lib/lafcadio/util/ContextualService.rb +0 -33
  83. data/lib/lafcadio/util/English.rb +0 -117
  84. data/lib/lafcadio/util/HashOfArrays.rb +0 -48
  85. data/lib/lafcadio/util/LafcadioConfig.rb +0 -25
  86. data/lib/lafcadio/util/QueueHash.rb +0 -67
  87. data/lib/lafcadio/util/UsStates.rb +0 -29
  88. data/lib/lafcadio/xml.rb +0 -2
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'ubygems'
4
+ require 'lafcadio/depend'
5
+ require 'lafcadio/schema'
6
+ require 'lafcadio/util'
7
+ require 'getoptlong'
8
+ include Lafcadio
9
+
10
+ opts = GetoptLong.new( [ '--config', '-c', GetoptLong::REQUIRED_ARGUMENT ] )
11
+ if ( ( optArray = opts.get ) && ARGV.size >= 1 )
12
+ configFile = optArray[1]
13
+ LafcadioConfig.set_filename( configFile )
14
+ ARGV.each { |fileName|
15
+ require "#{ fileName }"
16
+ fileName =~ /(\w*)\.rb/
17
+ className = $1
18
+ domainClass = Class.get_class( className )
19
+ if domainClass
20
+ statement = CreateTableStatement.new( domainClass )
21
+ puts statement.to_sql
22
+ else
23
+ puts "No domain class in #{ fileName }"
24
+ end
25
+ }
26
+ else
27
+ puts "lafcadio_schema -c config domain1.rb {, domain2.rb, domain3.rb ... }"
28
+ end
@@ -13,14 +13,14 @@
13
13
  # overridden.
14
14
  #
15
15
  # First-time users are recommended to read the tutorial at
16
- # http://rubyforge.lafcadio.org/tutorial.html.
16
+ # http://lafcadio.rubyforge.org/tutorial.html.
17
17
 
18
18
  module Lafcadio
19
- Version = "0.4.3"
19
+ Version = "0.5.2"
20
20
 
21
21
  require 'lafcadio/dateTime'
22
+ require 'lafcadio/depend'
22
23
  require 'lafcadio/domain'
23
- require 'lafcadio/includer'
24
24
  require 'lafcadio/mock'
25
25
  require 'lafcadio/objectField'
26
26
  require 'lafcadio/objectStore'
@@ -28,5 +28,4 @@ module Lafcadio
28
28
  require 'lafcadio/schema'
29
29
  require 'lafcadio/test'
30
30
  require 'lafcadio/util'
31
- require 'lafcadio/xml'
32
31
  end
@@ -13,14 +13,14 @@
13
13
  # overridden.
14
14
  #
15
15
  # First-time users are recommended to read the tutorial at
16
- # http://rubyforge.lafcadio.org/tutorial.html.
16
+ # http://lafcadio.rubyforge.org/tutorial.html.
17
17
 
18
18
  module Lafcadio
19
- Version = "0.4.2"
19
+ Version = "0.5.1"
20
20
 
21
21
  require 'lafcadio/dateTime'
22
+ require 'lafcadio/depend'
22
23
  require 'lafcadio/domain'
23
- require 'lafcadio/includer'
24
24
  require 'lafcadio/mock'
25
25
  require 'lafcadio/objectField'
26
26
  require 'lafcadio/objectStore'
@@ -28,5 +28,4 @@ module Lafcadio
28
28
  require 'lafcadio/schema'
29
29
  require 'lafcadio/test'
30
30
  require 'lafcadio/util'
31
- require 'lafcadio/xml'
32
31
  end
@@ -1,3 +1,5 @@
1
+ require 'lafcadio/depend'
2
+
1
3
  dir = Dir.new 'lafcadio/'
2
4
  dir.each { |entry|
3
5
  if ![ '.', '..', 'CVS' ].index(entry) && entry !~ /~$/
@@ -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
+ }
@@ -1,2 +1,93 @@
1
- require 'lafcadio/includer'
2
- Includer.include( 'dateTime' )
1
+ module Lafcadio
2
+ # Represents a specific month in time. With the exception of Month.month_names
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.month_names
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( year = nil, month = 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
+ # Returns a new Month that is +amountToAdd+ months later.
32
+ def +( amountToAdd )
33
+ ( fullYears, remainingMonths ) = amountToAdd.divmod( 12 )
34
+ resultYear = @year + fullYears
35
+ resultMonth = @month + remainingMonths
36
+ if resultMonth > 12
37
+ resultMonth -= 12
38
+ resultYear += 1
39
+ end
40
+ Month.new( resultYear, resultMonth )
41
+ end
42
+
43
+ # Returns a new Month that is +amountToSubtract+ months earlier.
44
+ def -(amountToSubtract)
45
+ self + (-amountToSubtract)
46
+ end
47
+
48
+ # Compare this Month to another Month.
49
+ def <=>(anOther)
50
+ if @year == anOther.year
51
+ @month <=> anOther.month
52
+ else
53
+ @year <=> anOther.year
54
+ end
55
+ end
56
+
57
+ # Returns the last Date of the month.
58
+ def end_date
59
+ self.next.start_date - 1
60
+ end
61
+
62
+ # Is this Month equal to +anOther+? +anOther+ must be another Month of the
63
+ # same value.
64
+ def eql?(anOther)
65
+ self == anOther
66
+ end
67
+
68
+ # Calculate a hash value for this Month.
69
+ def hash
70
+ "#{@year}#{@month}".to_i
71
+ end
72
+
73
+ # Returns the next Month.
74
+ def next
75
+ self + 1
76
+ end
77
+
78
+ # Returns the previous Month.
79
+ def prev
80
+ self - 1
81
+ end
82
+
83
+ # Returns the first Date of the month.
84
+ def start_date
85
+ Date.new( @year, @month, 1 )
86
+ end
87
+
88
+ # Returns a string of the format "January 2001".
89
+ def to_s
90
+ Month.month_names[@month-1][0..2] + " " + @year.to_s
91
+ end
92
+ end
93
+ end
@@ -1,11 +1,11 @@
1
1
  module Lafcadio
2
- # Represents a specific month in time. With the exception of Month.monthNames
2
+ # Represents a specific month in time. With the exception of Month.month_names
3
3
  # (which returns a zero-based array), every usage of the month value assumes
4
4
  # that 1 equals January and 12 equals December.
5
5
  class Month
6
6
  # Returns an array of the full names of months (in English). Note that
7
7
  # "January" is the 0th element, and "December" is the 11th element.
8
- def Month.monthNames
8
+ def Month.month_names
9
9
  [ "January", "February", "March", "April", "May", "June", "July",
10
10
  "August", "September", "October", "November", "December" ]
11
11
  end
@@ -28,6 +28,23 @@ module Lafcadio
28
28
  @year = year
29
29
  end
30
30
 
31
+ # Returns a new Month that is +amountToAdd+ months later.
32
+ def +( amountToAdd )
33
+ ( fullYears, remainingMonths ) = amountToAdd.divmod( 12 )
34
+ resultYear = @year + fullYears
35
+ resultMonth = @month + remainingMonths
36
+ if resultMonth > 12
37
+ resultMonth -= 12
38
+ resultYear += 1
39
+ end
40
+ Month.new( resultMonth, resultYear )
41
+ end
42
+
43
+ # Returns a new Month that is +amountToSubtract+ months earlier.
44
+ def -(amountToSubtract)
45
+ self + (-amountToSubtract)
46
+ end
47
+
31
48
  # Compare this Month to another Month.
32
49
  def <=>(anOther)
33
50
  if @year == anOther.year
@@ -37,14 +54,9 @@ module Lafcadio
37
54
  end
38
55
  end
39
56
 
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
57
+ # Returns the last Date of the month.
58
+ def end_date
59
+ self.next.start_date - 1
48
60
  end
49
61
 
50
62
  # Is this Month equal to +anOther+? +anOther+ must be another Month of the
@@ -53,21 +65,14 @@ module Lafcadio
53
65
  self == anOther
54
66
  end
55
67
 
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 )
68
+ # Calculate a hash value for this Month.
69
+ def hash
70
+ "#{@year}#{@month}".to_i
66
71
  end
67
-
68
- # Returns a new Month that is +amountToSubtract+ months earlier.
69
- def -(amountToSubtract)
70
- self + (-amountToSubtract)
72
+
73
+ # Returns the next Month.
74
+ def next
75
+ self + 1
71
76
  end
72
77
 
73
78
  # Returns the previous Month.
@@ -75,19 +80,14 @@ module Lafcadio
75
80
  self - 1
76
81
  end
77
82
 
78
- # Returns the next Month.
79
- def next
80
- self + 1
81
- end
82
-
83
83
  # Returns the first Date of the month.
84
- def startDate
84
+ def start_date
85
85
  Date.new( @year, @month, 1 )
86
86
  end
87
87
 
88
- # Returns the last Date of the month.
89
- def endDate
90
- self.next.startDate - 1
88
+ # Returns a string of the format "January 2001".
89
+ def to_s
90
+ Month.month_names[@month-1][0..2] + " " + @year.to_s
91
91
  end
92
92
  end
93
93
  end
@@ -0,0 +1,3 @@
1
+ require 'dbi'
2
+ require 'log4r'
3
+ require 'runit/testcase'
@@ -1,37 +1,101 @@
1
- require 'lafcadio/includer'
2
- Includer.include( 'domain' )
3
1
  require 'lafcadio/objectField'
4
- require 'lafcadio/util'
2
+ require 'lafcadio/util'
5
3
  require 'rexml/document'
6
4
 
7
5
  module Lafcadio
8
6
  class ClassDefinitionXmlParser # :nodoc: all
9
- class InvalidDataError < ArgumentError; end
7
+ def initialize( domain_class, xml )
8
+ @domain_class = domain_class
9
+ @xmlDocRoot = REXML::Document.new( xml ).root
10
+ @namesProcessed = {}
11
+ end
12
+
13
+ def get_class_field( fieldElt )
14
+ className = fieldElt.attributes['class'].to_s
15
+ name = fieldElt.attributes['name']
16
+ begin
17
+ fieldClass = Class.get_class( 'Lafcadio::' + className )
18
+ register_name( name )
19
+ field = fieldClass.instantiate_from_xml( @domain_class, fieldElt )
20
+ set_field_attributes( field, fieldElt )
21
+ rescue MissingError
22
+ msg = "Couldn't find field class '#{ className }' for field " +
23
+ "'#{ name }'"
24
+ raise( MissingError, msg, caller )
25
+ end
26
+ field
27
+ end
10
28
 
29
+ def get_class_fields
30
+ namesProcessed = {}
31
+ pk_field = PrimaryKeyField.new( @domain_class )
32
+ if ( spkn = @xmlDocRoot.attributes['sql_primary_key_name'] )
33
+ pk_field.db_field_name = spkn
34
+ end
35
+ fields = [ pk_field ]
36
+ @xmlDocRoot.elements.each('field') { |fieldElt|
37
+ fields << get_class_field( fieldElt )
38
+ }
39
+ fields
40
+ end
41
+
42
+ def possible_field_attributes
43
+ fieldAttr = []
44
+ fieldAttr << FieldAttribute.new( 'size', FieldAttribute::INTEGER )
45
+ fieldAttr << FieldAttribute.new( 'unique', FieldAttribute::BOOLEAN )
46
+ fieldAttr << FieldAttribute.new( 'not_null', FieldAttribute::BOOLEAN )
47
+ fieldAttr << FieldAttribute.new( 'enum_type', FieldAttribute::ENUM,
48
+ BooleanField )
49
+ fieldAttr << FieldAttribute.new( 'enums', FieldAttribute::HASH )
50
+ fieldAttr << FieldAttribute.new( 'range', FieldAttribute::ENUM,
51
+ DateField )
52
+ fieldAttr << FieldAttribute.new( 'large', FieldAttribute::BOOLEAN )
53
+ end
54
+
55
+ def register_name( name )
56
+ raise InvalidDataError if @namesProcessed[name]
57
+ @namesProcessed[name] = true
58
+ end
59
+
60
+ def set_field_attributes( field, fieldElt )
61
+ possible_field_attributes.each { |fieldAttr|
62
+ fieldAttr.maybe_set_field_attr( field, fieldElt )
63
+ }
64
+ end
65
+
66
+ def table_name
67
+ @xmlDocRoot.attributes['table_name']
68
+ end
69
+
11
70
  class FieldAttribute
12
71
  INTEGER = 1
13
72
  BOOLEAN = 2
14
73
  ENUM = 3
15
74
  HASH = 4
16
75
 
17
- attr_reader :name, :valueClass
76
+ attr_reader :name, :value_class
18
77
 
19
- def initialize( name, valueClass, objectFieldClass = nil )
20
- @name = name; @valueClass = valueClass
78
+ def initialize( name, value_class, objectFieldClass = nil )
79
+ @name = name; @value_class = value_class
21
80
  @objectFieldClass = objectFieldClass
22
81
  end
23
82
 
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 }"
83
+ def maybe_set_field_attr( field, fieldElt )
84
+ setterMethod = "#{ name }="
85
+ if field.respond_to?( setterMethod )
86
+ if value_class != FieldAttribute::HASH
87
+ if ( attrStr = fieldElt.attributes[name] )
88
+ field.send( setterMethod, value_from_string( attrStr ) )
89
+ end
90
+ else
91
+ if ( attrElt = fieldElt.elements[name] )
92
+ field.send( setterMethod, value_from_elt( attrElt ) )
93
+ end
94
+ end
31
95
  end
32
96
  end
33
-
34
- def valueFromElt( elt )
97
+
98
+ def value_from_elt( elt )
35
99
  hash = {}
36
100
  elt.elements.each( English.singular( @name ) ) { |subElt|
37
101
  key = subElt.attributes['key'] == 'true'
@@ -41,79 +105,519 @@ module Lafcadio
41
105
  hash
42
106
  end
43
107
 
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
108
+ def value_from_string( valueStr )
109
+ if @value_class == INTEGER
110
+ valueStr.to_i
111
+ elsif @value_class == BOOLEAN
112
+ valueStr == 'y'
113
+ elsif @value_class == ENUM
114
+ eval "#{ @objectFieldClass.name }::#{ valueStr }"
56
115
  end
57
116
  end
58
117
  end
59
118
 
60
- def initialize( domainClass, xml )
61
- @domainClass = domainClass
62
- @xmlDocRoot = REXML::Document.new( xml ).root
63
- @namesProcessed = {}
119
+ class InvalidDataError < ArgumentError; end
120
+ end
121
+
122
+ module DomainComparable
123
+ include Comparable
124
+
125
+ # A DomainObject or DomainObjectProxy is compared by +object_type+ and by
126
+ # +pk_id+.
127
+ def <=>(anOther)
128
+ if anOther.respond_to?( 'object_type' )
129
+ if self.object_type == anOther.object_type
130
+ self.pk_id <=> anOther.pk_id
131
+ else
132
+ self.object_type.name <=> anOther.object_type.name
133
+ end
134
+ else
135
+ nil
136
+ end
64
137
  end
65
138
 
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 )
139
+ def eql?(otherObj)
140
+ self == otherObj
141
+ end
142
+
143
+ def hash; "#{ self.class.name } #{ pk_id }".hash; end
144
+ end
145
+
146
+ # All classes that correspond to a table in the database need to be children
147
+ # of DomainObject.
148
+ #
149
+ # = Defining fields
150
+ # There are two ways to define the fields of a DomainObject subclass.
151
+ # 1. Defining fields in an XML file. To do this,
152
+ # 1. Set one directory to contain all your XML files, by setting
153
+ # +classDefinitionDir+ in your LafcadioConfig file.
154
+ # 2. Write one XML file per domain class. For example, a User.xml file
155
+ # might look like:
156
+ # <lafcadio_class_definition name="User">
157
+ # <field name="lastName" class="TextField"/>
158
+ # <field name="email" class="TextField"/>
159
+ # <field name="password" class="TextField"/>
160
+ # <field name="birthday" class="DateField"/>
161
+ # </lafcadio_class_definition>
162
+ # 2. Overriding DomainObject.get_class_fields. The method should return an Array
163
+ # of instances of ObjectField or its children. The order is unimportant.
164
+ # For example:
165
+ # class User < DomainObject
166
+ # def User.get_class_fields
167
+ # fields = []
168
+ # fields << TextField.new(self, 'firstName')
169
+ # fields << TextField.new(self, 'lastName')
170
+ # fields << TextField.new(self, 'email')
171
+ # fields << TextField.new(self, 'password')
172
+ # fields << DateField.new(self, 'birthday')
173
+ # fields
174
+ # end
175
+ # end
176
+ #
177
+ # = Setting and retrieving fields
178
+ # Once your fields are defined, you can create an instance by passing in a
179
+ # hash of field names and values.
180
+ # john = User.new( 'firstName' => 'John', 'lastName' => 'Doe',
181
+ # 'email' => 'john.doe@email.com',
182
+ # 'password' => 'my_password',
183
+ # 'birthday' => tenYearsAgo )
184
+ #
185
+ # You can read and write these fields like normal instance attributes.
186
+ # john.email => 'john.doe@email.com'
187
+ # john.email = 'john.doe@mail.email.com'
188
+ #
189
+ # If your domain class has fields that refer to other domain classes, or even
190
+ # to another row in the same table, you can use a LinkField to express the
191
+ # relation.
192
+ # <lafcadio_class_definition name="Message">
193
+ # <field name="subject" class="TextField" />
194
+ # <field name="body" class="TextField" />
195
+ # <field name="author" class="LinkField" linked_type="User" />
196
+ # <field name="recipient" class="LinkField" linked_type="User" />
197
+ # <field name="dateSent" class="DateField" />
198
+ # </lafcadio_class_definition>
199
+ #
200
+ # msg = Message.new( 'subject' => 'hi there',
201
+ # 'body' => 'You wanna go to the movies on Saturday?',
202
+ # 'author' => john, 'recipient' => jane,
203
+ # 'dateSent' => Date.today )
204
+ #
205
+ # = pk_id and committing
206
+ # Lafcadio requires that each table has a numeric primary key. It assumes that
207
+ # this key is named +pk_id+ in the database, though that can be overridden.
208
+ #
209
+ # When you create a domain object by calling new, you should not assign a
210
+ # +pk_id+ to the new instance. The pk_id will automatically be set when you
211
+ # commit the object by calling DomainObject#commit.
212
+ #
213
+ # However, you may want to manually set +pk_id+ when setting up a test case, so
214
+ # you can ensure that a domain object has a given primary key.
215
+ #
216
+ # = Naming assumptions, and how to override them
217
+ # By default, Lafcadio assumes that every domain object is indexed by the
218
+ # field +pk_id+ in the database schema. If you're dealing with a table that
219
+ # uses a different field name, override DomainObject.sql_primary_key_name.
220
+ # However, you will always use +pk_id+ in your Ruby code.
221
+ #
222
+ # Lafcadio assumes that a domain class corresponds to a table whose name is
223
+ # the plural of the class name, and whose first letter is lowercase. A User
224
+ # class is assumed to be stored in a "users" table, while a ProductCategory
225
+ # class is assumed to be stored in a "productCategories" table. Override
226
+ # DomainObject.table_name to override this behavior.
227
+ #
228
+ # = Inheritance
229
+ # Domain classes can inherit from other domain classes; they have all the
230
+ # fields of any concrete superclasses plus any new fields defined for
231
+ # themselves. You can use normal inheritance to define this:
232
+ # class User < DomainObject
233
+ # ...
234
+ # end
235
+ #
236
+ # class Administrator < User
237
+ # ...
238
+ # end
239
+ #
240
+ # Lafcadio assumes that each concrete class has a corresponding table, and
241
+ # that each table has a +pk_id+ field that is used to match rows between
242
+ # different levels.
243
+ class DomainObject
244
+ @@subclassHash = {}
245
+ @@class_fields = {}
246
+
247
+ COMMIT_ADD = 1
248
+ COMMIT_EDIT = 2
249
+ COMMIT_DELETE = 3
250
+
251
+ include DomainComparable
252
+
253
+ def self.abstract_subclasses #:nodoc:
254
+ require 'lafcadio/domain'
255
+ [ MapObject ]
256
+ end
257
+
258
+ # Returns an array of all fields defined for this class and all concrete
259
+ # superclasses.
260
+ def self.all_fields
261
+ all_fields = []
262
+ self_and_concrete_superclasses.each { |aClass|
263
+ aClass.class_fields.each { |field| all_fields << field }
264
+ }
265
+ all_fields
266
+ end
267
+
268
+ def self.class_fields #:nodoc:
269
+ class_fields = @@class_fields[self]
270
+ unless class_fields
271
+ @@class_fields[self] = self.get_class_fields
272
+ class_fields = @@class_fields[self]
80
273
  end
274
+ class_fields
275
+ end
276
+
277
+ def self.create_field( field_class, name, att_hash )
278
+ class_fields = @@class_fields[self]
279
+ if class_fields.nil?
280
+ class_fields = [ PrimaryKeyField.new( self ) ]
281
+ @@class_fields[self] = class_fields
282
+ end
283
+ att_hash['name'] = name
284
+ field = field_class.instantiate_with_parameters( self, att_hash )
285
+ att_hash.each { |field_name, value|
286
+ setter = field_name + '='
287
+ field.send( setter, value ) if field.respond_to?( setter )
288
+ }
289
+ class_fields << field
290
+ end
291
+
292
+ def self.dependent_classes #:nodoc:
293
+ dependent_classes = {}
294
+ DomainObject.subclasses.each { |aClass|
295
+ if aClass != DomainObjectProxy &&
296
+ (!DomainObject.abstract_subclasses.index(aClass))
297
+ aClass.class_fields.each { |field|
298
+ if field.class <= LinkField && field.linked_type == self.object_type
299
+ dependent_classes[aClass] = field
300
+ end
301
+ }
302
+ end
303
+ }
304
+ dependent_classes
305
+ end
306
+
307
+ def self.get_class_field(fieldName) #:nodoc:
308
+ field = nil
309
+ self.class_fields.each { |aField|
310
+ field = aField if aField.name == fieldName
311
+ }
81
312
  field
82
313
  end
314
+
315
+ def DomainObject.get_class_field_by_db_name( fieldName ) #:nodoc:
316
+ self.class_fields.find { |field| field.db_field_name == fieldName }
317
+ end
83
318
 
84
- def getClassFields
85
- namesProcessed = {}
86
- fields = []
87
- @xmlDocRoot.elements.each('field') { |fieldElt|
88
- fields << get_class_field( fieldElt )
319
+ # Returns an Array of ObjectField instances for this domain class, parsing
320
+ # them from XML if necessary.
321
+ def self.get_class_fields
322
+ if self.methods( false ).include?( 'get_class_fields' )
323
+ [ PrimaryKeyField.new( self ) ]
324
+ else
325
+ xmlParser = try_load_xml_parser
326
+ if xmlParser
327
+ xmlParser.get_class_fields
328
+ else
329
+ error_msg = "Couldn't find either an XML class description file " +
330
+ "or get_class_fields method for " + self.name
331
+ raise MissingError, error_msg, caller
332
+ end
333
+ end
334
+ end
335
+
336
+ def self.get_domain_dirs #:nodoc:
337
+ config = LafcadioConfig.new
338
+ classPath = config['classpath']
339
+ domainDirStr = config['domainDirs']
340
+ if domainDirStr
341
+ domainDirs = domainDirStr.split(',')
342
+ else
343
+ domainDirs = [ classPath + 'domain/' ]
344
+ end
345
+ end
346
+
347
+ def self.get_field( fieldName ) #:nodoc:
348
+ aDomainClass = self
349
+ field = nil
350
+ while aDomainClass < DomainObject && !field
351
+ field = aDomainClass.get_class_field( fieldName )
352
+ aDomainClass = aDomainClass.superclass
353
+ end
354
+ if field
355
+ field
356
+ else
357
+ errStr = "Couldn't find field \"#{ field }\" in " +
358
+ "#{ self } domain class"
359
+ raise( MissingError, errStr, caller )
360
+ end
361
+ end
362
+
363
+ def self.get_object_type_from_string(typeString) #:nodoc:
364
+ object_type = nil
365
+ require_domain_file( typeString )
366
+ subclasses.each { |subclass|
367
+ object_type = subclass if subclass.to_s == typeString
89
368
  }
90
- fields
369
+ if object_type
370
+ object_type
371
+ else
372
+ raise CouldntMatchObjectTypeError,
373
+ "couldn't match object_type #{typeString}", caller
374
+ end
375
+ end
376
+
377
+ def self.inherited(subclass) #:nodoc:
378
+ @@subclassHash[subclass] = true
91
379
  end
92
380
 
93
- def register_name( name )
94
- raise InvalidDataError if @namesProcessed[name]
95
- @namesProcessed[name] = true
381
+ def self.is_based_on? #:nodoc:
382
+ self.superclass.is_concrete?
383
+ end
384
+
385
+ def self.is_concrete? #:nodoc:
386
+ (self != DomainObject && abstract_subclasses.index(self).nil?)
387
+ end
388
+
389
+ def self.method_missing( methodId, *args ) #:nodoc:
390
+ method_name = methodId.id2name
391
+ maybe_field_class_name = ( method_name.gsub( /^(.)/ ) { $&.upcase } ) +
392
+ 'Field'
393
+ field_class = Lafcadio.const_get( maybe_field_class_name )
394
+ create_field( field_class, args[0], args[1] || {} )
395
+ end
396
+
397
+ def self.object_type #:nodoc:
398
+ self
399
+ end
400
+
401
+ def self.require_domain_file( typeString )
402
+ typeString =~ /([^\:]*)$/
403
+ fileName = $1
404
+ get_domain_dirs.each { |domainDir|
405
+ if Dir.entries(domainDir).index("#{fileName}.rb")
406
+ require "#{ domainDir }#{ fileName }"
407
+ end
408
+ }
409
+ if (domainFilesStr = LafcadioConfig.new['domainFiles'])
410
+ domainFilesStr.split(',').each { |domainFile|
411
+ require domainFile
412
+ }
413
+ end
414
+ end
415
+
416
+ def self.self_and_concrete_superclasses # :nodoc:
417
+ classes = [ ]
418
+ anObjectType = self
419
+ until(anObjectType == DomainObject ||
420
+ abstract_subclasses.index(anObjectType) != nil)
421
+ classes << anObjectType
422
+ anObjectType = anObjectType.superclass
423
+ end
424
+ classes
425
+ end
426
+
427
+ # Returns the name of the primary key in the database, retrieving it from
428
+ # the class definition XML if necessary.
429
+ def self.sql_primary_key_name( set_sql_primary_key_name = nil )
430
+ if set_sql_primary_key_name
431
+ get_field( 'pk_id' ).db_field_name = set_sql_primary_key_name
432
+ end
433
+ get_field( 'pk_id' ).db_field_name
434
+ end
435
+
436
+ def self.subclasses #:nodoc:
437
+ @@subclassHash.keys
438
+ end
439
+
440
+ # Returns the table name, which is assumed to be the domain class name
441
+ # pluralized, and with the first letter lowercase. A User class is
442
+ # assumed to be stored in a "users" table, while a ProductCategory class is
443
+ # assumed to be stored in a "productCategories" table.
444
+ def self.table_name( set_table_name = nil )
445
+ if set_table_name
446
+ @table_name = set_table_name
447
+ elsif @table_name
448
+ @table_name
449
+ else
450
+ xmlParser = try_load_xml_parser
451
+ if (!xmlParser.nil? && table_name = xmlParser.table_name)
452
+ table_name
453
+ else
454
+ table_name = self.bare_name
455
+ table_name[0] = table_name[0..0].downcase
456
+ English.plural table_name
457
+ end
458
+ end
459
+ end
460
+
461
+ def self.try_load_xml_parser
462
+ require 'lafcadio/domain'
463
+ dirName = LafcadioConfig.new['classDefinitionDir']
464
+ xmlFileName = self.bare_name + '.xml'
465
+ xmlPath = File.join( dirName, xmlFileName )
466
+ xml = ''
467
+ begin
468
+ File.open( xmlPath ) { |file| xml = file.readlines.join }
469
+ ClassDefinitionXmlParser.new( self, xml )
470
+ rescue Errno::ENOENT
471
+ # no xml file, so no @xmlParser
472
+ end
96
473
  end
97
474
 
98
- def sqlPrimaryKeyName
99
- @xmlDocRoot.attributes['sqlPrimaryKeyName']
475
+ attr_accessor :error_messages, :last_commit, :fields, :fields_set
476
+ attr_reader :delete
477
+ protected :fields, :fields_set
478
+
479
+ # fieldHash should contain key-value associations for the different
480
+ # fields of this domain class. For example, instantiating a User class
481
+ # might look like:
482
+ #
483
+ # User.new( 'firstNames' => 'John', 'lastName' => 'Doe',
484
+ # 'email' => 'john.doe@email.com', 'password' => 'l33t' )
485
+ #
486
+ # In normal usage any code you write that creates a domain object will not
487
+ # define the +pk_id+ field. The system assumes that a domain object with an
488
+ # undefined +pk_id+ has yet to be inserted into the database, and when you
489
+ # commit the domain object a +pk_id+ will automatically be assigned.
490
+ #
491
+ # If you're creating mock objects for unit tests, you can explicitly set
492
+ # the +pk_id+ to represent objects that already exist in the database.
493
+ def initialize(fieldHash)
494
+ @fieldHash = fieldHash
495
+ @error_messages = []
496
+ @fields = {}
497
+ @fields_set = []
498
+ check_fields = LafcadioConfig.new()['checkFields']
499
+ verify if %w( onInstantiate onAllStates ).include?( check_fields )
100
500
  end
101
501
 
102
- def tableName
103
- @xmlDocRoot.attributes['tableName']
502
+ # Returns a clone, with all of the fields copied.
503
+ def clone
504
+ copy = super
505
+ copy.fields = @fields.clone
506
+ copy.fields_set = @fields_set.clone
507
+ copy
104
508
  end
105
509
 
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 )
510
+ # Commits this domain object to the database.
511
+ def commit
512
+ ObjectStore.get_object_store.commit self
513
+ end
514
+
515
+ # Set the delete value to true if you want this domain object to be deleted
516
+ # from the database during its next commit.
517
+ def delete=(value)
518
+ if value && !pk_id
519
+ raise "No point deleting an object that's not already in the DB"
520
+ end
521
+ @delete = value
522
+ end
523
+
524
+ def get_field( field ) #:nodoc:
525
+ unless @fields_set.include?( field )
526
+ set_field( field, @fieldHash[field.name] )
527
+ end
528
+ @fields[field.name]
529
+ end
530
+
531
+ def get_getter_field( methId ) #:nodoc:
532
+ begin
533
+ self.class.get_field( methId.id2name )
534
+ rescue MissingError
535
+ nil
536
+ end
537
+ end
538
+
539
+ def get_setter_field( methId ) #:nodoc:
540
+ if methId.id2name =~ /(.*)=$/
541
+ begin
542
+ self.class.get_field( $1 )
543
+ rescue MissingError
544
+ nil
545
+ end
546
+ else
547
+ nil
548
+ end
549
+ end
550
+
551
+ def method_missing( methId, *args ) #:nodoc:
552
+ if ( field = get_setter_field( methId ) )
553
+ set_field( field, args.first )
554
+ elsif ( field = get_getter_field( methId ) )
555
+ get_field( field )
556
+ elsif ( methId.to_s =~ /^get_/ and
557
+ ObjectStore.get_object_store.respond_to?( methId ) )
558
+ args = [ self ].concat( args )
559
+ ObjectStore.get_object_store.send( methId, *args )
560
+ else
561
+ super( methId, *args )
562
+ end
563
+ end
564
+
565
+ # Returns the subclass of DomainObject that this instance represents.
566
+ # Because of the way that proxying works, clients should call this method
567
+ # instead of Object.class.
568
+ def object_type
569
+ self.class.object_type
570
+ end
571
+
572
+ # This template method is called before every commit. Subclasses can
573
+ # override it to ensure code is executed before a commit.
574
+ def pre_commit_trigger
575
+ nil
576
+ end
577
+
578
+ # This template method is called after every commit. Subclasses can
579
+ # override it to ensure code is executed after a commit.
580
+ def post_commit_trigger
581
+ nil
582
+ end
583
+
584
+ def set_field( field, value ) #:nodoc:
585
+ if field.class <= LinkField
586
+ if value.class != DomainObjectProxy && value
587
+ value = DomainObjectProxy.new(value)
588
+ end
589
+ end
590
+ if ( LafcadioConfig.new()['checkFields'] == 'onAllStates' &&
591
+ !field.instance_of?( PrimaryKeyField ) )
592
+ field.verify( value, pk_id )
593
+ end
594
+ @fields[field.name] = value
595
+ @fields_set << field
596
+ end
597
+
598
+ def verify
599
+ self.class.get_class_fields.each { |field|
600
+ field.verify( self.send( field.name ), self.pk_id )
601
+ }
602
+ end
603
+ end
604
+
605
+ # Any domain class that is used mostly to map between two other domain
606
+ # classes should be a subclass of MapObject. Subclasses of MapObject should
607
+ # override MapObject.mappedTypes, returning a two-element array containing
608
+ # the domain classes that the map object maps between.
609
+ class MapObject < DomainObject
610
+ def self.other_mapped_type(firstType) #:nodoc:
611
+ types = mappedTypes
612
+ if types.index(firstType) == 0
613
+ types[1]
614
+ else
615
+ types[0]
616
+ end
617
+ end
618
+
619
+ def self.subsidiary_map #:nodoc:
620
+ nil
117
621
  end
118
622
  end
119
623
  end