lafcadio 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. data/lib/lafcadio.rb +32 -0
  2. data/lib/lafcadio.rb~ +32 -0
  3. data/lib/lafcadio/TestSuite.rb +16 -0
  4. data/lib/lafcadio/dateTime.rb +2 -0
  5. data/lib/lafcadio/dateTime/Month.rb +93 -0
  6. data/lib/lafcadio/domain.rb +119 -0
  7. data/lib/lafcadio/domain.rb~ +119 -0
  8. data/lib/lafcadio/domain/DomainObject.rb +375 -0
  9. data/lib/lafcadio/domain/DomainObject.rb~ +371 -0
  10. data/lib/lafcadio/domain/MapObject.rb +22 -0
  11. data/lib/lafcadio/domain/ObjectType.rb +80 -0
  12. data/lib/lafcadio/includer.rb +18 -0
  13. data/lib/lafcadio/mock.rb +2 -0
  14. data/lib/lafcadio/mock/MockDbBridge.rb +78 -0
  15. data/lib/lafcadio/mock/MockDbBridge.rb~ +74 -0
  16. data/lib/lafcadio/mock/MockObjectStore.rb +20 -0
  17. data/lib/lafcadio/objectField.rb +14 -0
  18. data/lib/lafcadio/objectField/AutoIncrementField.rb +25 -0
  19. data/lib/lafcadio/objectField/BooleanField.rb +83 -0
  20. data/lib/lafcadio/objectField/DateField.rb +33 -0
  21. data/lib/lafcadio/objectField/DateTimeField.rb +25 -0
  22. data/lib/lafcadio/objectField/DecimalField.rb +41 -0
  23. data/lib/lafcadio/objectField/EmailField.rb +28 -0
  24. data/lib/lafcadio/objectField/EnumField.rb +62 -0
  25. data/lib/lafcadio/objectField/FieldValueError.rb +4 -0
  26. data/lib/lafcadio/objectField/IntegerField.rb +15 -0
  27. data/lib/lafcadio/objectField/LinkField.rb +92 -0
  28. data/lib/lafcadio/objectField/LinkField.rb~ +86 -0
  29. data/lib/lafcadio/objectField/MoneyField.rb +13 -0
  30. data/lib/lafcadio/objectField/MonthField.rb +16 -0
  31. data/lib/lafcadio/objectField/ObjectField.rb +142 -0
  32. data/lib/lafcadio/objectField/PasswordField.rb +29 -0
  33. data/lib/lafcadio/objectField/StateField.rb +13 -0
  34. data/lib/lafcadio/objectField/SubsetLinkField.rb +25 -0
  35. data/lib/lafcadio/objectField/TextField.rb +23 -0
  36. data/lib/lafcadio/objectField/TextListField.rb +21 -0
  37. data/lib/lafcadio/objectField/TimeStampField.rb +15 -0
  38. data/lib/lafcadio/objectStore.rb +100 -0
  39. data/lib/lafcadio/objectStore/Cache.rb +81 -0
  40. data/lib/lafcadio/objectStore/Committer.rb +65 -0
  41. data/lib/lafcadio/objectStore/CouldntMatchObjectTypeError.rb +4 -0
  42. data/lib/lafcadio/objectStore/DbBridge.rb +140 -0
  43. data/lib/lafcadio/objectStore/DbBridge.rb~ +140 -0
  44. data/lib/lafcadio/objectStore/DomainComparable.rb +25 -0
  45. data/lib/lafcadio/objectStore/DomainObjectInitError.rb +9 -0
  46. data/lib/lafcadio/objectStore/DomainObjectNotFoundError.rb +4 -0
  47. data/lib/lafcadio/objectStore/DomainObjectProxy.rb +62 -0
  48. data/lib/lafcadio/objectStore/DomainObjectSqlMaker.rb +74 -0
  49. data/lib/lafcadio/objectStore/ObjectStore.rb +207 -0
  50. data/lib/lafcadio/objectStore/ObjectStore.rb~ +207 -0
  51. data/lib/lafcadio/objectStore/SqlValueConverter.rb +30 -0
  52. data/lib/lafcadio/objectStore/SqlValueConverter.rb~ +30 -0
  53. data/lib/lafcadio/query.rb +203 -0
  54. data/lib/lafcadio/query/Compare.rb +55 -0
  55. data/lib/lafcadio/query/CompoundCondition.rb +39 -0
  56. data/lib/lafcadio/query/Condition.rb +66 -0
  57. data/lib/lafcadio/query/Condition.rb~ +66 -0
  58. data/lib/lafcadio/query/Equals.rb +45 -0
  59. data/lib/lafcadio/query/In.rb +20 -0
  60. data/lib/lafcadio/query/Like.rb +48 -0
  61. data/lib/lafcadio/query/Link.rb +20 -0
  62. data/lib/lafcadio/query/Max.rb +32 -0
  63. data/lib/lafcadio/query/Max.rb~ +25 -0
  64. data/lib/lafcadio/query/Not.rb +21 -0
  65. data/lib/lafcadio/query/Query.rb +92 -0
  66. data/lib/lafcadio/schema.rb +2 -0
  67. data/lib/lafcadio/schema/CreateTableStatement.rb +61 -0
  68. data/lib/lafcadio/schema/CreateTableStatement.rb~ +59 -0
  69. data/lib/lafcadio/test.rb +2 -0
  70. data/lib/lafcadio/test/LafcadioTestCase.rb +17 -0
  71. data/lib/lafcadio/test/testconfig.dat +13 -0
  72. data/lib/lafcadio/util.rb +180 -0
  73. data/lib/lafcadio/util/Context.rb +61 -0
  74. data/lib/lafcadio/util/ContextualService.rb +33 -0
  75. data/lib/lafcadio/util/English.rb +117 -0
  76. data/lib/lafcadio/util/HashOfArrays.rb +48 -0
  77. data/lib/lafcadio/util/LafcadioConfig.rb +25 -0
  78. data/lib/lafcadio/util/QueueHash.rb +67 -0
  79. data/lib/lafcadio/util/UsStates.rb +29 -0
  80. data/lib/lafcadio/xml.rb +2 -0
  81. metadata +135 -0
@@ -0,0 +1,92 @@
1
+ module Lafcadio
2
+ class Query #:nodoc:
3
+ ASC = 1
4
+ DESC = 2
5
+
6
+ attr_reader :objectType, :condition
7
+ attr_accessor :orderBy, :orderByOrder, :limit
8
+
9
+ def initialize(objectType, pkIdOrCondition = nil)
10
+ @objectType = objectType
11
+ ( @condition, @orderBy, @limit ) = [ nil, nil, nil ]
12
+ if pkIdOrCondition
13
+ if pkIdOrCondition.class <= Condition
14
+ @condition = pkIdOrCondition
15
+ else
16
+ @condition = Query::Equals.new(objectType.sqlPrimaryKeyName,
17
+ pkIdOrCondition, objectType)
18
+ end
19
+ end
20
+ @orderByOrder = ASC
21
+ end
22
+
23
+ def hash; toSql.hash; end
24
+
25
+ def eql?( other )
26
+ other.class <= Query && other.toSql == toSql
27
+ end
28
+
29
+ def tables
30
+ tableNames = []
31
+ anObjectType = objectType
32
+ until(DomainObject.abstractSubclasses.index(anObjectType) != nil ||
33
+ anObjectType == DomainObject)
34
+ tableNames.unshift anObjectType.tableName
35
+ anObjectType = anObjectType.superclass
36
+ end
37
+ tableNames.join ', '
38
+ end
39
+
40
+ def whereClause
41
+ whereClauses = []
42
+ anObjectType = objectType
43
+ superclass = anObjectType.superclass
44
+ until(DomainObject.abstractSubclasses.index(superclass) != nil ||
45
+ superclass == DomainObject)
46
+ joinClause = "#{ sqlPrimaryKeyField(superclass) } = " +
47
+ "#{ sqlPrimaryKeyField(anObjectType) }"
48
+ whereClauses.unshift joinClause
49
+ anObjectType = superclass
50
+ superclass = superclass.superclass
51
+ end
52
+ whereClauses << @condition.toSql if @condition
53
+ if whereClauses.size > 0
54
+ "where #{ whereClauses.join(' and ') }"
55
+ else
56
+ nil
57
+ end
58
+ end
59
+
60
+ def sqlPrimaryKeyField(objectType)
61
+ "#{ objectType.tableName }.#{ objectType.sqlPrimaryKeyName }"
62
+ end
63
+
64
+ def fields
65
+ '*'
66
+ end
67
+
68
+ def orderClause
69
+ if @orderBy
70
+ clause = "order by #{ @orderBy } "
71
+ if @orderByOrder == ASC
72
+ clause += 'asc'
73
+ else
74
+ clause += 'desc'
75
+ end
76
+ clause
77
+ end
78
+ end
79
+
80
+ def limitClause
81
+ "limit #{ @limit.begin }, #{ @limit.end - @limit.begin + 1 }" if @limit
82
+ end
83
+
84
+ def toSql
85
+ clauses = [ "select #{ fields }", "from #{ tables }" ]
86
+ clauses << whereClause if whereClause
87
+ clauses << orderClause if orderClause
88
+ clauses << limitClause if limitClause
89
+ clauses.join ' '
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,2 @@
1
+ require 'lafcadio/includer'
2
+ Includer.include( 'schema' )
@@ -0,0 +1,61 @@
1
+ module Lafcadio
2
+ class CreateTableStatement #:nodoc:
3
+ def initialize( domainClass )
4
+ @domainClass = domainClass
5
+ end
6
+
7
+ def typeClause( field )
8
+ require 'lafcadio/objectField/TextField'
9
+ require 'lafcadio/objectField/DecimalField'
10
+ require 'lafcadio/objectField/LinkField'
11
+ require 'lafcadio/objectField/IntegerField'
12
+ require 'lafcadio/objectField/DateField'
13
+ require 'lafcadio/objectField/EnumField'
14
+ require 'lafcadio/objectField/TextListField'
15
+ require 'lafcadio/objectField/BooleanField'
16
+ require 'lafcadio/objectField/DateTimeField'
17
+ if ( field.class <= EnumField )
18
+ singleQuotedValues = field.enums.keys.collect! { |enumValue|
19
+ "'#{ enumValue }'"
20
+ }
21
+ "enum( #{ singleQuotedValues.join( ', ' ) } )"
22
+ elsif ( field.class <= TextField || field.class <= TextListField )
23
+ 'varchar(255)'
24
+ elsif field.class <= DecimalField
25
+ "float(10, #{ field.precision })"
26
+ elsif ( field.class <= LinkField || field.class <= IntegerField )
27
+ 'int'
28
+ elsif field.class <= DateField
29
+ 'date'
30
+ elsif field.class <= BooleanField
31
+ 'bool'
32
+ elsif field.class <= TimeStampField
33
+ 'timestamp'
34
+ elsif field.class <= DateTimeField
35
+ 'datetime'
36
+ elsif field.class <= BlobField
37
+ 'blob'
38
+ end
39
+ end
40
+
41
+ def toSql
42
+ createDefinitions = []
43
+ createDefinitions << "#{ @domainClass.sqlPrimaryKeyName } " +
44
+ "int not null auto_increment"
45
+ createDefinitions << "primary key (#{ @domainClass.sqlPrimaryKeyName })"
46
+ @domainClass.classFields.each { |field|
47
+ definitionTerms = []
48
+ definitionTerms << field.dbFieldName
49
+ definitionTerms << typeClause( field )
50
+ definitionTerms << 'not null' if field.notNull
51
+ definitionTerms << 'unique' if field.unique
52
+ createDefinitions << definitionTerms.join(' ')
53
+ }
54
+ <<-SQL
55
+ create table #{ @domainClass.tableName } (
56
+ #{ createDefinitions.join(",\n ") }
57
+ );
58
+ SQL
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,59 @@
1
+ module Lafcadio
2
+ class CreateTableStatement #:nodoc:
3
+ def initialize( domainClass )
4
+ @domainClass = domainClass
5
+ end
6
+
7
+ def typeClause( field )
8
+ require 'lafcadio/objectField/TextField'
9
+ require 'lafcadio/objectField/DecimalField'
10
+ require 'lafcadio/objectField/LinkField'
11
+ require 'lafcadio/objectField/IntegerField'
12
+ require 'lafcadio/objectField/DateField'
13
+ require 'lafcadio/objectField/EnumField'
14
+ require 'lafcadio/objectField/TextListField'
15
+ require 'lafcadio/objectField/BooleanField'
16
+ require 'lafcadio/objectField/DateTimeField'
17
+ if ( field.class <= EnumField )
18
+ singleQuotedValues = field.enums.keys.collect! { |enumValue|
19
+ "'#{ enumValue }'"
20
+ }
21
+ "enum( #{ singleQuotedValues.join( ', ' ) } )"
22
+ elsif ( field.class <= TextField || field.class <= TextListField )
23
+ 'varchar(255)'
24
+ elsif field.class <= DecimalField
25
+ "float(10, #{ field.precision })"
26
+ elsif ( field.class <= LinkField || field.class <= IntegerField )
27
+ 'int'
28
+ elsif field.class <= DateField
29
+ 'date'
30
+ elsif field.class <= BooleanField
31
+ 'bool'
32
+ elsif field.class <= TimeStampField
33
+ 'timestamp'
34
+ elsif field.class <= DateTimeField
35
+ 'datetime'
36
+ end
37
+ end
38
+
39
+ def toSql
40
+ createDefinitions = []
41
+ createDefinitions << "#{ @domainClass.sqlPrimaryKeyName } " +
42
+ "int not null auto_increment"
43
+ createDefinitions << "primary key (#{ @domainClass.sqlPrimaryKeyName })"
44
+ @domainClass.classFields.each { |field|
45
+ definitionTerms = []
46
+ definitionTerms << field.dbFieldName
47
+ definitionTerms << typeClause( field )
48
+ definitionTerms << 'not null' if field.notNull
49
+ definitionTerms << 'unique' if field.unique
50
+ createDefinitions << definitionTerms.join(' ')
51
+ }
52
+ <<-SQL
53
+ create table #{ @domainClass.tableName } (
54
+ #{ createDefinitions.join(",\n ") }
55
+ );
56
+ SQL
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,2 @@
1
+ require 'lafcadio/includer'
2
+ Includer.include( 'test' )
@@ -0,0 +1,17 @@
1
+ require 'runit/testcase'
2
+ require 'lafcadio/mock/MockObjectStore'
3
+ require 'lafcadio/util/LafcadioConfig'
4
+
5
+ # A test case that sets up a number of mock services. In writing an application
6
+ # that uses Lafcadio you may find it convenient to inherit from this class.
7
+ class LafcadioTestCase < RUNIT::TestCase
8
+ include Lafcadio
9
+
10
+ def setup
11
+ context = Context.instance
12
+ context.flush
13
+ @mockObjectStore = MockObjectStore.new context
14
+ context.setObjectStore @mockObjectStore
15
+ LafcadioConfig.setFilename 'lafcadio/test/testConfig.dat'
16
+ end
17
+ end
@@ -0,0 +1,13 @@
1
+ dbuser:test
2
+ dbpassword:password
3
+ dbname:test
4
+ dbhost:localhost
5
+ adminEmail:john.doe@email.com
6
+ siteName:Site Name
7
+ url:http://test.url
8
+ classpath:lafcadio/
9
+ logdir:../test/testOutput/
10
+ domainDirs:lafcadio/domain/,../test/mock/domain/
11
+ domainFiles:../test/mock/domain.rb
12
+ logSql:n
13
+ classDefinitionDir:../test/testData
@@ -0,0 +1,180 @@
1
+ require 'lafcadio/includer'
2
+ Includer.include( 'util' )
3
+
4
+ class Array
5
+ # If this array has one element, returns that element; otherwise, raises an
6
+ # error.
7
+ def only
8
+ if size != 1
9
+ raise "Expected single-value Array but Array has #{ size } members"
10
+ else
11
+ first
12
+ end
13
+ end
14
+ end
15
+
16
+ class Class < Module
17
+ # Given a String, returns a class object by the same name.
18
+ def self.getClass(className)
19
+ theClass = nil
20
+ ObjectSpace.each_object(Class) { |aClass|
21
+ theClass = aClass if aClass.name == className
22
+ }
23
+ if theClass
24
+ theClass
25
+ else
26
+ raise( Lafcadio::MissingError, "Couldn't find class \"#{ className }\"",
27
+ caller )
28
+ end
29
+ end
30
+
31
+ # Returns the name of <tt>aClass</tt> itself, stripping off the names of any
32
+ # containing modules or outer classes.
33
+ def bareName
34
+ name =~ /::/
35
+ $' || name
36
+ end
37
+ end
38
+
39
+ module Lafcadio
40
+ class MissingError < RuntimeError
41
+ end
42
+ end
43
+
44
+ class Numeric
45
+ # Returns a string that represents the numbeer to <tt>precision</tt> decimal
46
+ # places, rounding down if necessary. If <tt>padDecimals</tt> is set to
47
+ # <tt>false</tt> and the number rounds to a whole number, there will be no
48
+ # decimals shown.
49
+ #
50
+ # (24.55).precisionFormat( 3 ) -> "24.550"
51
+ # (24.55).precisionFormat( 0 ) -> "24"
52
+ # 100.precisionFormat( 2, false ) -> "100"
53
+ def precisionFormat(precision, padDecimals = true)
54
+ str = floor.to_s
55
+ if precision > 0
56
+ decimal = self - self.floor
57
+ decimalStr =(decimal * 10 ** precision).floor.to_s
58
+ if decimalStr.size < precision
59
+ decimalStr += "0" *(precision - decimalStr.size)
60
+ end
61
+ if padDecimals || decimalStr =~ /[123456789]/
62
+ str += "."
63
+ str += decimalStr
64
+ end
65
+ end
66
+ str
67
+ end
68
+ end
69
+
70
+ class String
71
+ # Returns the number of times that <tt>regexp</tt> occurs in the string.
72
+ def countOccurrences(regexp)
73
+ count = 0
74
+ str = self.clone
75
+ while str =~ regexp
76
+ count += 1
77
+ str = $'
78
+ end
79
+ count
80
+ end
81
+
82
+ # Decapitalizes the first letter of the string, or decapitalizes the
83
+ # entire string if it's all capitals.
84
+ #
85
+ # 'InternalClient'.decapitalize -> "internalClient"
86
+ # 'SKU'.decapitalize -> "sku"
87
+ def decapitalize
88
+ string = clone
89
+ firstLetter = string[0..0].downcase
90
+ string = firstLetter + string[1..string.length]
91
+ newString = ""
92
+ while string =~ /([A-Z])([^a-z]|$)/
93
+ newString += $`
94
+ newString += $1.downcase
95
+ string = $2 + $'
96
+ end
97
+ newString += string
98
+ newString
99
+ end
100
+
101
+ # Increments a filename. If the filename ends with a number, it increments
102
+ # that number; otherwise it appends a "_1" after the filename but before the
103
+ # file extension.
104
+ #
105
+ # "john.jpg".incrementFilename -> "john_1.jpg"
106
+ # "john_1.jpg".incrementFilename -> "john_2.jpg"
107
+ # "john_2.jpg".incrementFilename -> "john_3.jpg"
108
+ def incrementFilename
109
+ filename = self.clone
110
+ extension = filename.split(/\./).last
111
+ filename.sub!(/\..*$/, '')
112
+ if filename =~ /_(\d*)$/
113
+ newSuffix = $1.to_i + 1
114
+ filename = $` + "_#{newSuffix}"
115
+ else
116
+ filename += "_1"
117
+ end
118
+ filename += ".#{extension}"
119
+ filename
120
+ end
121
+
122
+ # Breaks a string into lines no longer than <tt>lineLength</tt>.
123
+ #
124
+ # 'the quick brown fox jumped over the lazy dog.'.lineWrap( 10 ) ->
125
+ # "the quick\nbrown fox\njumped\nover the\nlazy dog."
126
+ def lineWrap(lineLength)
127
+ words = split ' '
128
+ line = ''
129
+ lines = []
130
+ words.each { |word|
131
+ if line.length + word.length + 1 > lineLength
132
+ lines << line
133
+ line = ''
134
+ end
135
+ line = line != '' ? "#{ line } #{ word }" : word
136
+ }
137
+ lines << line
138
+ lines.join "\n"
139
+ end
140
+
141
+ # Turns a numeric string into U.S. format if it's not already formatted that
142
+ # way.
143
+ #
144
+ # "10,00".numericStringToUsFormat -> "10.00"
145
+ # "10.00".numericStringToUsFormat -> "10.00"
146
+ def numericStringToUsFormat
147
+ numericString = clone
148
+ numericString.gsub!(/,/, '.') if numericString =~ /,\d{2}$/
149
+ numericString
150
+ end
151
+
152
+ # Left-pads a string with +fillChar+ up to +size+ size.
153
+ #
154
+ # "a".pad( 10, "+") -> "+++++++++a"
155
+ def pad(size, fillChar)
156
+ string = clone
157
+ while string.length < size
158
+ string = fillChar + string
159
+ end
160
+ string
161
+ end
162
+
163
+ # Divides a string into substrings using <tt>regexp</tt> as a
164
+ # delimiter, and returns an array containing both the substrings and the
165
+ # portions that matched <tt>regexp</tt>.
166
+ #
167
+ # 'theZquickZZbrownZfox'.splitKeepInBetweens(/Z+/) ->
168
+ # ['the', 'Z', 'quick', 'ZZ', 'brown', 'Z', 'fox' ]
169
+ def splitKeepInBetweens(regexp)
170
+ result = []
171
+ string = clone
172
+ while string =~ regexp
173
+ result << $`
174
+ result << $&
175
+ string = $'
176
+ end
177
+ result << string unless string == ''
178
+ result
179
+ end
180
+ end
@@ -0,0 +1,61 @@
1
+ require 'singleton'
2
+
3
+ module Lafcadio
4
+ # The Context is a singleton object that manages ContextualServices. Each
5
+ # ContextualService is a service that connects in some way to external
6
+ # resources: ObjectStore connects to the database; Emailer connects to SMTP,
7
+ # etc.
8
+ #
9
+ # Context makes it easy to ensure that each ContextualService is only
10
+ # instantiated once, which can be quite useful for services with expensive
11
+ # creation.
12
+ #
13
+ # Furthermore, Context allows you to explicitly set instances for a given
14
+ # service, which can be quite useful in testing. For example, once
15
+ # LafcadioTestCase#setup has an instance of MockObjectStore, it calls
16
+ # context.setObjectStore @mockObjectStore
17
+ # which ensures that any future calls to ObjectStore.getObjectStore will
18
+ # return @mockObjectStore, instead of an instance of ObjectStore connecting
19
+ # test code to a live database.
20
+ class Context
21
+ include Singleton
22
+
23
+ def initialize
24
+ @resources = {}
25
+ end
26
+
27
+ # Flushes all cached ContextualServices.
28
+ def flush
29
+ @resources = {}
30
+ end
31
+
32
+ def createInstance(resourceName) #:nodoc:
33
+ resourceClass = eval resourceName
34
+ resourceClass.new self
35
+ end
36
+
37
+ def getResource(resourceName) #:nodoc:
38
+ resource = @resources[resourceName]
39
+ unless resource
40
+ resource = createInstance resourceName
41
+ setResource resourceName, resource
42
+ end
43
+ resource
44
+ end
45
+
46
+ def setResource(resourceName, resource) #:nodoc:
47
+ @resources[resourceName] = resource
48
+ end
49
+
50
+ def method_missing(methId, *args) #:nodoc:
51
+ methodName = methId.id2name
52
+ if methodName =~ /^get(.*)$/
53
+ getResource $1
54
+ elsif methodName =~ /^set(.*)$/
55
+ setResource $1, args[0]
56
+ else
57
+ super
58
+ end
59
+ end
60
+ end
61
+ end