lafcadio 0.4.3 → 0.5.2

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