lafcadio 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/lib/lafcadio.rb +32 -0
- data/lib/lafcadio.rb~ +32 -0
- data/lib/lafcadio/TestSuite.rb +16 -0
- data/lib/lafcadio/dateTime.rb +2 -0
- data/lib/lafcadio/dateTime/Month.rb +93 -0
- data/lib/lafcadio/domain.rb +119 -0
- data/lib/lafcadio/domain.rb~ +119 -0
- data/lib/lafcadio/domain/DomainObject.rb +375 -0
- data/lib/lafcadio/domain/DomainObject.rb~ +371 -0
- data/lib/lafcadio/domain/MapObject.rb +22 -0
- data/lib/lafcadio/domain/ObjectType.rb +80 -0
- data/lib/lafcadio/includer.rb +18 -0
- data/lib/lafcadio/mock.rb +2 -0
- data/lib/lafcadio/mock/MockDbBridge.rb +78 -0
- data/lib/lafcadio/mock/MockDbBridge.rb~ +74 -0
- data/lib/lafcadio/mock/MockObjectStore.rb +20 -0
- data/lib/lafcadio/objectField.rb +14 -0
- data/lib/lafcadio/objectField/AutoIncrementField.rb +25 -0
- data/lib/lafcadio/objectField/BooleanField.rb +83 -0
- data/lib/lafcadio/objectField/DateField.rb +33 -0
- data/lib/lafcadio/objectField/DateTimeField.rb +25 -0
- data/lib/lafcadio/objectField/DecimalField.rb +41 -0
- data/lib/lafcadio/objectField/EmailField.rb +28 -0
- data/lib/lafcadio/objectField/EnumField.rb +62 -0
- data/lib/lafcadio/objectField/FieldValueError.rb +4 -0
- data/lib/lafcadio/objectField/IntegerField.rb +15 -0
- data/lib/lafcadio/objectField/LinkField.rb +92 -0
- data/lib/lafcadio/objectField/LinkField.rb~ +86 -0
- data/lib/lafcadio/objectField/MoneyField.rb +13 -0
- data/lib/lafcadio/objectField/MonthField.rb +16 -0
- data/lib/lafcadio/objectField/ObjectField.rb +142 -0
- data/lib/lafcadio/objectField/PasswordField.rb +29 -0
- data/lib/lafcadio/objectField/StateField.rb +13 -0
- data/lib/lafcadio/objectField/SubsetLinkField.rb +25 -0
- data/lib/lafcadio/objectField/TextField.rb +23 -0
- data/lib/lafcadio/objectField/TextListField.rb +21 -0
- data/lib/lafcadio/objectField/TimeStampField.rb +15 -0
- data/lib/lafcadio/objectStore.rb +100 -0
- data/lib/lafcadio/objectStore/Cache.rb +81 -0
- data/lib/lafcadio/objectStore/Committer.rb +65 -0
- data/lib/lafcadio/objectStore/CouldntMatchObjectTypeError.rb +4 -0
- data/lib/lafcadio/objectStore/DbBridge.rb +140 -0
- data/lib/lafcadio/objectStore/DbBridge.rb~ +140 -0
- data/lib/lafcadio/objectStore/DomainComparable.rb +25 -0
- data/lib/lafcadio/objectStore/DomainObjectInitError.rb +9 -0
- data/lib/lafcadio/objectStore/DomainObjectNotFoundError.rb +4 -0
- data/lib/lafcadio/objectStore/DomainObjectProxy.rb +62 -0
- data/lib/lafcadio/objectStore/DomainObjectSqlMaker.rb +74 -0
- data/lib/lafcadio/objectStore/ObjectStore.rb +207 -0
- data/lib/lafcadio/objectStore/ObjectStore.rb~ +207 -0
- data/lib/lafcadio/objectStore/SqlValueConverter.rb +30 -0
- data/lib/lafcadio/objectStore/SqlValueConverter.rb~ +30 -0
- data/lib/lafcadio/query.rb +203 -0
- data/lib/lafcadio/query/Compare.rb +55 -0
- data/lib/lafcadio/query/CompoundCondition.rb +39 -0
- data/lib/lafcadio/query/Condition.rb +66 -0
- data/lib/lafcadio/query/Condition.rb~ +66 -0
- data/lib/lafcadio/query/Equals.rb +45 -0
- data/lib/lafcadio/query/In.rb +20 -0
- data/lib/lafcadio/query/Like.rb +48 -0
- data/lib/lafcadio/query/Link.rb +20 -0
- data/lib/lafcadio/query/Max.rb +32 -0
- data/lib/lafcadio/query/Max.rb~ +25 -0
- data/lib/lafcadio/query/Not.rb +21 -0
- data/lib/lafcadio/query/Query.rb +92 -0
- data/lib/lafcadio/schema.rb +2 -0
- data/lib/lafcadio/schema/CreateTableStatement.rb +61 -0
- data/lib/lafcadio/schema/CreateTableStatement.rb~ +59 -0
- data/lib/lafcadio/test.rb +2 -0
- data/lib/lafcadio/test/LafcadioTestCase.rb +17 -0
- data/lib/lafcadio/test/testconfig.dat +13 -0
- data/lib/lafcadio/util.rb +180 -0
- data/lib/lafcadio/util/Context.rb +61 -0
- data/lib/lafcadio/util/ContextualService.rb +33 -0
- data/lib/lafcadio/util/English.rb +117 -0
- data/lib/lafcadio/util/HashOfArrays.rb +48 -0
- data/lib/lafcadio/util/LafcadioConfig.rb +25 -0
- data/lib/lafcadio/util/QueueHash.rb +67 -0
- data/lib/lafcadio/util/UsStates.rb +29 -0
- data/lib/lafcadio/xml.rb +2 -0
- 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,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
|