lafcadio 0.4.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
@@ -0,0 +1,207 @@
|
|
1
|
+
require 'lafcadio'
|
2
|
+
|
3
|
+
module Lafcadio
|
4
|
+
# The ObjectStore represents the database in a Lafcadio application.
|
5
|
+
#
|
6
|
+
# = Configuring the ObjectStore
|
7
|
+
# The ObjectStore depends on a few values being set correctly in the
|
8
|
+
# LafcadioConfig file:
|
9
|
+
# [dbuser] The database username.
|
10
|
+
# [dbpassword] The database password.
|
11
|
+
# [dbname] The database name.
|
12
|
+
# [dbhost] The database host.
|
13
|
+
#
|
14
|
+
# = Instantiating ObjectStore
|
15
|
+
# The ObjectStore is a ContextualService, meaning you can't get an instance by
|
16
|
+
# calling ObjectStore.new. Instead, you should call
|
17
|
+
# ObjectStore.getObjectStore. (Using a ContextualService makes it easier to
|
18
|
+
# make out the ObjectStore for unit tests: See ContextualService for more.)
|
19
|
+
#
|
20
|
+
# = Dynamic method calls
|
21
|
+
# ObjectStore uses reflection to provide a lot of convenience methods for
|
22
|
+
# querying domain objects in a number of ways.
|
23
|
+
# [ObjectStore#get< domain class > (pkId)]
|
24
|
+
# Retrieves one domain object by pkId. For example,
|
25
|
+
# ObjectStore#getUser( 100 )
|
26
|
+
# will return User 100.
|
27
|
+
# [ObjectStore#get< domain class >s (searchTerm, fieldName = nil)]
|
28
|
+
# Returns a collection of all instances of that domain class matching that
|
29
|
+
# search term. For example,
|
30
|
+
# ObjectStore#getProducts( aProductCategory )
|
31
|
+
# queries MySQL for all products that belong to that product category. You
|
32
|
+
# can omit +fieldName+ if +searchTerm+ is a non-nil domain object, and the
|
33
|
+
# field connecting the first domain class to the second is named after the
|
34
|
+
# domain class. (For example, the above line assumes that Product has a
|
35
|
+
# field named "productCategory".) Otherwise, it's best to include
|
36
|
+
# +fieldName+:
|
37
|
+
# ObjectStore#getUsers( "Jones", "lastName" )
|
38
|
+
#
|
39
|
+
# = Querying
|
40
|
+
# ObjectStore can also be used to generate complex, ad-hoc queries which
|
41
|
+
# emulate much of the functionality you'd get from writing the SQL yourself.
|
42
|
+
# Furthermore, these queries can be run against in-memory data stores, which
|
43
|
+
# is particularly useful for tests.
|
44
|
+
# date = Date.new( 2003, 1, 1 )
|
45
|
+
# ObjectStore#getInvoices { |invoice|
|
46
|
+
# Query.And( invoice.date.gte( date ), invoice.rate.equals( 10 ),
|
47
|
+
# invoice.hours.equals( 10 ) )
|
48
|
+
# }
|
49
|
+
# is the same as
|
50
|
+
# select * from invoices
|
51
|
+
# where (date >= '2003-01-01' and rate = 10 and hours = 10)
|
52
|
+
# See lafcadio/query.rb for more.
|
53
|
+
#
|
54
|
+
# = SQL Logging
|
55
|
+
# Lafcadio uses log4r to log all of its SQL statements. The simplest way to
|
56
|
+
# turn on logging is to set the following values in the LafcadioConfig file:
|
57
|
+
# [logSql] Should be set to "y" to turn on logging.
|
58
|
+
# [logdir] The directory where log files should be written. Required if
|
59
|
+
# +logSql+ is "y"
|
60
|
+
# [sqlLogFile] The name of the file (not including its directory) where SQL
|
61
|
+
# should be logged. Default is "sql".
|
62
|
+
#
|
63
|
+
# = Triggers
|
64
|
+
# Domain classes can be set to fire triggers either before or after commits.
|
65
|
+
# Since these triggers are executed in Ruby, they're easy to test. See
|
66
|
+
# DomainObject#preCommitTrigger and DomainObject#postCommitTrigger for more.
|
67
|
+
class ObjectStore < ContextualService
|
68
|
+
def ObjectStore.setDbName(dbName) #:nodoc:
|
69
|
+
DbBridge.setDbName dbName
|
70
|
+
end
|
71
|
+
|
72
|
+
def initialize(context, dbBridge = nil) #:nodoc:
|
73
|
+
super context
|
74
|
+
@dbBridge = dbBridge == nil ? DbBridge.new : dbBridge
|
75
|
+
@cache = ObjectStore::Cache.new( @dbBridge )
|
76
|
+
end
|
77
|
+
|
78
|
+
# Commits a domain object to the database. You can also simply call
|
79
|
+
# myDomainObject.commit
|
80
|
+
def commit(dbObject)
|
81
|
+
require 'lafcadio/objectStore/Committer'
|
82
|
+
committer = Committer.new dbObject, @dbBridge
|
83
|
+
committer.execute
|
84
|
+
updateCacheAfterCommit( committer )
|
85
|
+
end
|
86
|
+
|
87
|
+
# Flushes one domain object from its cache.
|
88
|
+
def flush(dbObject)
|
89
|
+
@cache.flush dbObject
|
90
|
+
end
|
91
|
+
|
92
|
+
# Returns the domain object corresponding to the domain class and pkId.
|
93
|
+
def get(objectType, pkId)
|
94
|
+
query = Query.new objectType, pkId
|
95
|
+
@cache.getByQuery( query )[0] ||
|
96
|
+
( raise( DomainObjectNotFoundError,
|
97
|
+
"Can't find #{objectType} #{pkId}", caller ) )
|
98
|
+
end
|
99
|
+
|
100
|
+
# Returns all domain objects for the given domain class.
|
101
|
+
def getAll(objectType)
|
102
|
+
query = Query.new( objectType )
|
103
|
+
@cache.getByQuery( query )
|
104
|
+
end
|
105
|
+
|
106
|
+
# Returns the DbBridge; this is useful in case you need to use raw SQL for a
|
107
|
+
# specific query.
|
108
|
+
def getDbBridge; @dbBridge; end
|
109
|
+
|
110
|
+
def getFiltered(objectTypeName, searchTerm, fieldName = nil) #:nodoc:
|
111
|
+
require 'lafcadio/query/Link'
|
112
|
+
objectType = DomainObject.getObjectTypeFromString objectTypeName
|
113
|
+
unless fieldName
|
114
|
+
fieldName = searchTerm.objectType.bareName
|
115
|
+
fieldName = fieldName.decapitalize
|
116
|
+
end
|
117
|
+
if searchTerm.class <= DomainObject
|
118
|
+
condition = Query::Link.new(fieldName, searchTerm, objectType)
|
119
|
+
else
|
120
|
+
condition = Query::Equals.new(fieldName, searchTerm, objectType)
|
121
|
+
end
|
122
|
+
getSubset( condition )
|
123
|
+
end
|
124
|
+
|
125
|
+
def getMapMatch(objectType, mapped) #:nodoc:
|
126
|
+
fieldName = mapped.objectType.bareName.decapitalize
|
127
|
+
Query::Equals.new(fieldName, mapped, objectType)
|
128
|
+
end
|
129
|
+
|
130
|
+
def getMapObject(objectType, map1, map2) #:nodoc:
|
131
|
+
require 'lafcadio/query/CompoundCondition'
|
132
|
+
unless map1 && map2
|
133
|
+
raise ArgumentError,
|
134
|
+
"ObjectStore#getMapObject needs two non-nil keys", caller
|
135
|
+
end
|
136
|
+
mapMatch1 = getMapMatch objectType, map1
|
137
|
+
mapMatch2 = getMapMatch objectType, map2
|
138
|
+
condition = Query::CompoundCondition.new mapMatch1, mapMatch2
|
139
|
+
getSubset(condition)[0]
|
140
|
+
end
|
141
|
+
|
142
|
+
def getMapped(searchTerm, resultTypeName) #:nodoc:
|
143
|
+
resultType = DomainObject.getObjectTypeFromString resultTypeName
|
144
|
+
coll = []
|
145
|
+
firstTypeName = searchTerm.class.bareName
|
146
|
+
secondTypeName = resultType.bareName
|
147
|
+
mapTypeName = firstTypeName + secondTypeName
|
148
|
+
getFiltered(mapTypeName, searchTerm).each { |mapObj|
|
149
|
+
coll << mapObj.send( resultType.name.decapitalize )
|
150
|
+
}
|
151
|
+
coll
|
152
|
+
end
|
153
|
+
|
154
|
+
# Retrieves the maximum value across all instances of one domain class.
|
155
|
+
# ObjectStore#getMax( Client )
|
156
|
+
# returns the highest +pkId+ in the +clients+ table.
|
157
|
+
# ObjectStore#getMax( Invoice, "rate" )
|
158
|
+
# will return the highest rate for all invoices.
|
159
|
+
def getMax( domain_class, field_name = 'pkId' )
|
160
|
+
query = Query::Max.new( domain_class, field_name )
|
161
|
+
@dbBridge.group_query( query ).only
|
162
|
+
end
|
163
|
+
|
164
|
+
# Retrieves a collection of domain objects by +pkId+.
|
165
|
+
# ObjectStore#getObjects( Clients, [ 1, 2, 3 ] )
|
166
|
+
def getObjects(objectType, pkIds)
|
167
|
+
require 'lafcadio/query/In'
|
168
|
+
condition = Query::In.new('pkId', pkIds, objectType)
|
169
|
+
getSubset condition
|
170
|
+
end
|
171
|
+
|
172
|
+
def getSubset(conditionOrQuery) #:nodoc:
|
173
|
+
if conditionOrQuery.class <= Query::Condition
|
174
|
+
condition = conditionOrQuery
|
175
|
+
query = Query.new condition.objectType, condition
|
176
|
+
else
|
177
|
+
query = conditionOrQuery
|
178
|
+
end
|
179
|
+
@cache.getByQuery( query )
|
180
|
+
end
|
181
|
+
|
182
|
+
def last_commit_time( domain_class, pkId ) #:nodoc:
|
183
|
+
@cache.last_commit_time( domain_class, pkId )
|
184
|
+
end
|
185
|
+
|
186
|
+
def method_missing(methodId, *args) #:nodoc:
|
187
|
+
proc = block_given? ? ( proc { |obj| yield( obj ) } ) : nil
|
188
|
+
dispatch = MethodDispatch.new( methodId, proc, *args )
|
189
|
+
self.send( dispatch.symbol, *dispatch.args )
|
190
|
+
end
|
191
|
+
|
192
|
+
# Caches one domain object.
|
193
|
+
def set(dbObject)
|
194
|
+
@cache.save dbObject
|
195
|
+
end
|
196
|
+
|
197
|
+
def updateCacheAfterCommit( committer ) #:nodoc:
|
198
|
+
if committer.commitType == Committer::UPDATE ||
|
199
|
+
committer.commitType == Committer::INSERT
|
200
|
+
set( committer.dbObject )
|
201
|
+
elsif committer.commitType == Committer::DELETE
|
202
|
+
@cache.flush( committer.dbObject )
|
203
|
+
end
|
204
|
+
@cache.set_commit_time( committer.dbObject )
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Lafcadio
|
2
|
+
class SqlValueConverter #:nodoc:
|
3
|
+
attr_reader :objectType, :rowHash
|
4
|
+
|
5
|
+
def initialize(objectType, rowHash)
|
6
|
+
@objectType = objectType
|
7
|
+
@rowHash = rowHash
|
8
|
+
end
|
9
|
+
|
10
|
+
def []( key )
|
11
|
+
if key == 'pkId'
|
12
|
+
if ( field_val = @rowHash[@objectType.sqlPrimaryKeyName] ).nil?
|
13
|
+
error_msg = "The field \"" + @objectType.sqlPrimaryKeyName +
|
14
|
+
"\" can\'t be found in the table \"" +
|
15
|
+
@objectType.tableName + "\"."
|
16
|
+
raise FieldMatchError, error_msg, caller
|
17
|
+
else
|
18
|
+
field_val.to_i
|
19
|
+
end
|
20
|
+
else
|
21
|
+
begin
|
22
|
+
field = @objectType.getField( key )
|
23
|
+
field.valueFromSQL( @rowHash[ field.dbFieldName ] )
|
24
|
+
rescue MissingError
|
25
|
+
nil
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Lafcadio
|
2
|
+
class SqlValueConverter #:nodoc:
|
3
|
+
attr_reader :objectType, :rowHash
|
4
|
+
|
5
|
+
def initialize(objectType, rowHash)
|
6
|
+
@objectType = objectType
|
7
|
+
@rowHash = rowHash
|
8
|
+
end
|
9
|
+
|
10
|
+
def []( key )
|
11
|
+
if key == 'pkId'
|
12
|
+
if ( field_val = @rowHash[@objectType.sqlPrimaryKeyName] ).nil?
|
13
|
+
error_msg = "The field \"" + @objectType.sqlPrimaryKeyName +
|
14
|
+
"\" can\'t be found in the table \"" +
|
15
|
+
@objectType.tableName + "\"."
|
16
|
+
raise FieldMatchError, error_msg, caller
|
17
|
+
else
|
18
|
+
field_val.to_i
|
19
|
+
end
|
20
|
+
else
|
21
|
+
begin
|
22
|
+
field = @objectType.getField( key )
|
23
|
+
field.valueFromSQL( @rowHash[ key ] )
|
24
|
+
rescue MissingError
|
25
|
+
nil
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,203 @@
|
|
1
|
+
# = Overview
|
2
|
+
# By passing a block to ObjectStore, you can write complex, ad-hoc queries in
|
3
|
+
# Ruby. This involves a few more keystrokes than writing raw SQL, but also makes
|
4
|
+
# it easier to change queries at runtime, and these queries can also be fully
|
5
|
+
# tested against the MockObjectStore.
|
6
|
+
# big_invoices = object_store.getInvoices { |inv| inv.rate.gt( 50 ) }
|
7
|
+
# # => "select * from invoices where rate > 50"
|
8
|
+
# This a full-fledged block, so you can pass in values from the calling context.
|
9
|
+
# date = Date.new( 2004, 1, 1 )
|
10
|
+
# recent_invoices = object_store.getInvoices { |inv| inv.date.gt( date ) }
|
11
|
+
# # => "select * from invoices where date > '2004-01-01'"
|
12
|
+
#
|
13
|
+
# = Query operators
|
14
|
+
# You can compare fields either to simple values, or to other fields in the same
|
15
|
+
# table.
|
16
|
+
# paid_immediately = object_store.getInvoices { |inv|
|
17
|
+
# inv.date.equals( inv.paid )
|
18
|
+
# }
|
19
|
+
# # => "select * from invoices where date = paid"
|
20
|
+
#
|
21
|
+
# == Numerical comparisons: +lt+, +lte+, +gte+, +gt+
|
22
|
+
# +lt+, +lte+, +gte+, and +gt+ stand for "less than", "less than or equal",
|
23
|
+
# "greater than or equal", and "greater than", respectively.
|
24
|
+
# tiny_invoices = object_store.getInvoices { |inv| inv.rate.lte( 25 ) }
|
25
|
+
# # => "select * from invoices where rate <= 25"
|
26
|
+
# These comparators work on fields that contain numbers, dates, and even
|
27
|
+
# references to other domain objects.
|
28
|
+
# for_1st_ten_clients = object_store.getInvoices { |inv|
|
29
|
+
# inv.client.lte( 10 )
|
30
|
+
# }
|
31
|
+
# # => "select * from invoices where client <= 10"
|
32
|
+
#
|
33
|
+
# == Equality: +equals+
|
34
|
+
# full_week_invs = object_store.getInvoices { |inv| inv.hours.equals( 40 ) }
|
35
|
+
# # => "select * from invoices where hours = 40"
|
36
|
+
# If you're comparing to a domain object you should pass in the object itself.
|
37
|
+
# client = object_store.getClient( 99 )
|
38
|
+
# invoices = object_store.getInvoices { |inv| inv.client.equals( client ) }
|
39
|
+
# # => "select * from invoices where client = 99"
|
40
|
+
#
|
41
|
+
# == Inclusion: +in+
|
42
|
+
# first_three_invs = object_store.getInvoices { |inv| inv.pkId.in( 1, 2, 3 ) }
|
43
|
+
# # => "select * from invoices where pkId in ( 1, 2, 3 )"
|
44
|
+
#
|
45
|
+
# == Text comparison: +like+
|
46
|
+
# fname_starts_with_a = object_store.getUsers { |user|
|
47
|
+
# user.fname.like( /^a/ )
|
48
|
+
# }
|
49
|
+
# # => "select * from users where fname like 'a%'"
|
50
|
+
# fname_ends_with_a = object_store.getUsers { |user|
|
51
|
+
# user.fname.like( /a$/ )
|
52
|
+
# }
|
53
|
+
# # => "select * from users where fname like '%a'"
|
54
|
+
# fname_contains_a = object_store.getUsers { |user|
|
55
|
+
# user.fname.like( /a/ )
|
56
|
+
# }
|
57
|
+
# # => "select * from users where fname like '%a%'"
|
58
|
+
# Please note that although we're using the Regexp operators here, these aren't
|
59
|
+
# full-fledged regexps. Only ^ and $ work for this.
|
60
|
+
#
|
61
|
+
# == Compound conditions: <tt>Query.And</tt> and <tt>Query.Or</tt>
|
62
|
+
# invoices = object_store.getInvoices { |inv|
|
63
|
+
# Query.And( inv.hours.equals( 40 ), inv.rate.equals( 50 ) )
|
64
|
+
# }
|
65
|
+
# # => "select * from invoices where (hours = 40 and rate = 50)"
|
66
|
+
# client99 = object_store.getClient( 99 )
|
67
|
+
# invoices = object_store.getInvoices { |inv|
|
68
|
+
# Query.Or( inv.hours.equals( 40 ),
|
69
|
+
# inv.rate.equals( 50 ),
|
70
|
+
# inv.client.equals( client99 ) )
|
71
|
+
# }
|
72
|
+
# # => "select * from invoices where (hours = 40 or rate = 50 or client = 99)"
|
73
|
+
# Note that both compound operators can take 2 or more arguments. Also, they can
|
74
|
+
# be nested:
|
75
|
+
# invoices = object_store.getInvoices { |inv|
|
76
|
+
# Query.And( inv.hours.equals( 40 ),
|
77
|
+
# Query.Or( inv.rate.equals( 50 ),
|
78
|
+
# inv.client.equals( client99 ) ) )
|
79
|
+
# }
|
80
|
+
# # => "select * from invoices where (hours = 40 and
|
81
|
+
# # (rate = 50 or client = 99))"
|
82
|
+
#
|
83
|
+
# == Negation: +not+
|
84
|
+
# invoices = object_store.getInvoices { |inv| inv.rate.equals( 50 ).not }
|
85
|
+
# # => "select * from invoices where rate != 50"
|
86
|
+
|
87
|
+
require 'lafcadio/includer'
|
88
|
+
Includer.include( 'query' )
|
89
|
+
|
90
|
+
module Lafcadio
|
91
|
+
class Query
|
92
|
+
def self.And( *conditions ); CompoundCondition.new( *conditions ); end
|
93
|
+
|
94
|
+
def self.Or( *conditions )
|
95
|
+
conditions << CompoundCondition::OR
|
96
|
+
CompoundCondition.new( *conditions)
|
97
|
+
end
|
98
|
+
|
99
|
+
class DomainObjectImpostor #:nodoc:
|
100
|
+
attr_reader :domainClass
|
101
|
+
|
102
|
+
def initialize( domainClass )
|
103
|
+
@domainClass = domainClass
|
104
|
+
end
|
105
|
+
|
106
|
+
def method_missing( methId, *args )
|
107
|
+
fieldName = methId.id2name
|
108
|
+
if fieldName == 'pkId'
|
109
|
+
ObjectFieldImpostor.new( self, fieldName )
|
110
|
+
else
|
111
|
+
begin
|
112
|
+
classField = @domainClass.getField( fieldName )
|
113
|
+
ObjectFieldImpostor.new( self, classField )
|
114
|
+
rescue MissingError
|
115
|
+
super( methId, *args )
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
class Inferrer #:nodoc:
|
122
|
+
def initialize( domainClass, &action )
|
123
|
+
@domainClass = domainClass; @action = action
|
124
|
+
end
|
125
|
+
|
126
|
+
def execute
|
127
|
+
impostor = DomainObjectImpostor.new( @domainClass )
|
128
|
+
condition = @action.call( impostor )
|
129
|
+
query = Query.new( @domainClass, condition )
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
class ObjectFieldImpostor #:nodoc:
|
134
|
+
def ObjectFieldImpostor.comparators
|
135
|
+
{
|
136
|
+
'lt' => Compare::LESS_THAN, 'lte' => Compare::LESS_THAN_OR_EQUAL,
|
137
|
+
'gte' => Compare::GREATER_THAN_OR_EQUAL,
|
138
|
+
'gt' => Compare::GREATER_THAN
|
139
|
+
}
|
140
|
+
end
|
141
|
+
|
142
|
+
attr_reader :class_field
|
143
|
+
|
144
|
+
def initialize( domainObjectImpostor, class_field_or_name )
|
145
|
+
@domainObjectImpostor = domainObjectImpostor
|
146
|
+
if class_field_or_name == 'pkId'
|
147
|
+
@db_field_name = 'pkId'
|
148
|
+
else
|
149
|
+
@class_field = class_field_or_name
|
150
|
+
@db_field_name = class_field_or_name.dbFieldName
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def method_missing( methId, *args )
|
155
|
+
methodName = methId.id2name
|
156
|
+
if !ObjectFieldImpostor.comparators.keys.index( methodName ).nil?
|
157
|
+
registerCompareCondition( methodName, *args )
|
158
|
+
else
|
159
|
+
super( methId, *args )
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def registerCompareCondition( compareStr, searchTerm)
|
164
|
+
compareVal = ObjectFieldImpostor.comparators[compareStr]
|
165
|
+
Compare.new( @db_field_name, searchTerm,
|
166
|
+
@domainObjectImpostor.domainClass, compareVal )
|
167
|
+
end
|
168
|
+
|
169
|
+
def equals( searchTerm )
|
170
|
+
Equals.new( @db_field_name, field_or_field_name( searchTerm ),
|
171
|
+
@domainObjectImpostor.domainClass )
|
172
|
+
end
|
173
|
+
|
174
|
+
def field_or_field_name( search_term )
|
175
|
+
if search_term.class == ObjectFieldImpostor
|
176
|
+
search_term.class_field
|
177
|
+
else
|
178
|
+
search_term
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def like( regexp )
|
183
|
+
if regexp.source =~ /^\^(.*)/
|
184
|
+
searchTerm = $1
|
185
|
+
matchType = Query::Like::POST_ONLY
|
186
|
+
elsif regexp.source =~ /(.*)\$$/
|
187
|
+
searchTerm = $1
|
188
|
+
matchType = Query::Like::PRE_ONLY
|
189
|
+
else
|
190
|
+
searchTerm = regexp.source
|
191
|
+
matchType = Query::Like::PRE_AND_POST
|
192
|
+
end
|
193
|
+
Query::Like.new( @db_field_name, searchTerm,
|
194
|
+
@domainObjectImpostor.domainClass, matchType )
|
195
|
+
end
|
196
|
+
|
197
|
+
def in( *searchTerms )
|
198
|
+
Query::In.new( @db_field_name, searchTerms,
|
199
|
+
@domainObjectImpostor.domainClass )
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|