lafcadio 0.9.0 → 0.9.1
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/lafcadio.rb +1 -1
- data/lib/lafcadio.rb~ +1 -1
- data/lib/lafcadio/domain.rb +363 -255
- data/lib/lafcadio/domain.rb~ +201 -230
- data/lib/lafcadio/mock.rb +64 -70
- data/lib/lafcadio/mock.rb~ +48 -49
- data/lib/lafcadio/objectField.rb +137 -127
- data/lib/lafcadio/objectField.rb~ +7 -7
- data/lib/lafcadio/objectStore.rb +560 -586
- data/lib/lafcadio/objectStore.rb~ +495 -536
- data/lib/lafcadio/query.rb +320 -213
- data/lib/lafcadio/query.rb~ +129 -131
- data/lib/lafcadio/schema.rb +10 -14
- data/lib/lafcadio/schema.rb~ +1 -1
- data/lib/lafcadio/test.rb +174 -75
- data/lib/lafcadio/test.rb~ +191 -0
- data/lib/lafcadio/util.rb +1 -32
- data/lib/lafcadio/util.rb~ +1 -6
- metadata +2 -2
data/lib/lafcadio/query.rb
CHANGED
@@ -1,19 +1,46 @@
|
|
1
1
|
# = Overview
|
2
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
|
4
|
-
# it easier to change queries at runtime, and these queries can also be
|
5
|
-
# tested against the MockObjectStore.
|
6
|
-
# big_invoices =
|
7
|
-
# # => "select * from invoices where rate > 50"
|
3
|
+
# Ruby. This involves a few more keystrokes than writing raw SQL, but also
|
4
|
+
# makes it easier to change queries at runtime, and these queries can also be
|
5
|
+
# fully tested against the MockObjectStore.
|
6
|
+
# big_invoices = Invoice.get { |inv| inv.rate.gt( 50 ) }
|
7
|
+
# # => runs "select * from invoices where rate > 50"
|
8
8
|
# This a full-fledged block, so you can pass in values from the calling context.
|
9
9
|
# date = Date.new( 2004, 1, 1 )
|
10
|
-
# recent_invoices =
|
11
|
-
# # => "select * from invoices where date > '2004-01-01'"
|
10
|
+
# recent_invoices = Invoice.get { |inv| inv.date.gt( date ) }
|
11
|
+
# # => runs "select * from invoices where date > '2004-01-01'"
|
12
|
+
#
|
13
|
+
# = Building and accessing queries
|
14
|
+
# To build a query and run it immediately, call DomainObject.get and pass it a
|
15
|
+
# block:
|
16
|
+
# hwangs = User.get { |u| u.lname.equals( 'Hwang' ) }
|
17
|
+
# You can also call ObjectStore#[ plural domain class ] with a block:
|
18
|
+
# hwangs = ObjectStore.get_object_store.users { |u|
|
19
|
+
# u.lname.equals( 'Hwang' )
|
20
|
+
# }
|
21
|
+
# If you want more fine-grained control over a query, first create it with
|
22
|
+
# Query.infer and then build it, using ObjectStore#query to run it.
|
23
|
+
# qry = Query.infer( User ) { |u| u.lname.equals( 'Hwang' ) }
|
24
|
+
# qry.to_sql # => "select * from users where users.lname = 'Hwang'"
|
25
|
+
# qry = qry.and { |u| u.fname.equals( 'Francis' ) }
|
26
|
+
# qry.to_sql # => "select * from users where (users.lname = 'Hwang' and
|
27
|
+
# users.fname = 'Francis')"
|
28
|
+
# qry.limit = 0..5
|
29
|
+
# qry.to_sql # => "select * from users where (users.lname = 'Hwang' and
|
30
|
+
# users.fname = 'Francis') limit 0, 6"
|
31
|
+
# Using Query.infer, you can also set order_by and order_by_order clauses:
|
32
|
+
# qry = Query.infer(
|
33
|
+
# SKU,
|
34
|
+
# :order_by => [ :standardPrice, :salePrice ],
|
35
|
+
# :order_by_order => Query::DESC
|
36
|
+
# ) { |s| s.sku.nil? }
|
37
|
+
# qry.to_sql # => "select * from skus where skus.sku is null order by
|
38
|
+
# standardPrice, salePrice desc"
|
12
39
|
#
|
13
|
-
# = Query operators
|
40
|
+
# = Query inference operators
|
14
41
|
# You can compare fields either to simple values, or to other fields in the same
|
15
42
|
# table.
|
16
|
-
# paid_immediately =
|
43
|
+
# paid_immediately = Invoice.get { |inv|
|
17
44
|
# inv.date.equals( inv.paid )
|
18
45
|
# }
|
19
46
|
# # => "select * from invoices where date = paid"
|
@@ -21,81 +48,121 @@
|
|
21
48
|
# == Numerical comparisons: +lt+, +lte+, +gte+, +gt+
|
22
49
|
# +lt+, +lte+, +gte+, and +gt+ stand for "less than", "less than or equal",
|
23
50
|
# "greater than or equal", and "greater than", respectively.
|
24
|
-
# tiny_invoices =
|
51
|
+
# tiny_invoices = Invoice.get { |inv| inv.rate.lte( 25 ) }
|
25
52
|
# # => "select * from invoices where rate <= 25"
|
26
53
|
# These comparators work on fields that contain numbers, dates, and even
|
27
54
|
# references to other domain objects.
|
28
|
-
# for_1st_ten_clients =
|
55
|
+
# for_1st_ten_clients = Invoice.get { |inv|
|
29
56
|
# inv.client.lte( 10 )
|
30
57
|
# }
|
31
58
|
# # => "select * from invoices where client <= 10"
|
59
|
+
# client10 = Client[10]
|
60
|
+
# for_1st_ten_clients = Invoice.get { |inv|
|
61
|
+
# inv.client.lte( client10 )
|
62
|
+
# }
|
63
|
+
# # => "select * from invoices where client <= 10"
|
32
64
|
#
|
33
65
|
# == Equality: +equals+
|
34
|
-
# full_week_invs =
|
66
|
+
# full_week_invs = Invoice.get { |inv| inv.hours.equals( 40 ) }
|
35
67
|
# # => "select * from invoices where hours = 40"
|
36
68
|
# If you're comparing to a domain object you should pass in the object itself.
|
37
|
-
# client =
|
38
|
-
# invoices =
|
69
|
+
# client = Client[99]
|
70
|
+
# invoices = Invoice.get { |inv| inv.client.equals( client ) }
|
39
71
|
# # => "select * from invoices where client = 99"
|
72
|
+
# If you're comparing to a boolean value you don't need to use
|
73
|
+
# <tt>equals( true )</tt>.
|
74
|
+
# administrators = User.get { |u| u.administrator.equals( true ) }
|
75
|
+
# administrators = User.get { |u| u.administrator } # both forms work
|
76
|
+
# Matching for +nil+ can use <tt>nil?</tt>
|
77
|
+
# no_email = User.get { |u| u.email.nil? }
|
40
78
|
#
|
41
|
-
# == Inclusion: +in+
|
42
|
-
#
|
79
|
+
# == Inclusion: +in+ and <tt>include?</tt>
|
80
|
+
# Any field can be matched via +in+:
|
81
|
+
# first_three_invs = Invoice.get { |inv| inv.pk_id.in( 1, 2, 3 ) }
|
43
82
|
# # => "select * from invoices where pk_id in ( 1, 2, 3 )"
|
83
|
+
# A TextListField can be matched via <tt>include?</tt>
|
84
|
+
# aim_users = User.get { |u| u.im_methods.include?( 'aim' ) }
|
85
|
+
# # => "select * from users where user.im_methods like 'aim,%' or
|
86
|
+
# user.im_methods like '%,aim,%' or user.im_methods like '%,aim' or
|
87
|
+
# user.im_methods = 'aim'"
|
44
88
|
#
|
45
89
|
# == Text comparison: +like+
|
46
|
-
# fname_starts_with_a =
|
47
|
-
# user.fname.like( /^a/ )
|
48
|
-
# }
|
90
|
+
# fname_starts_with_a = User.get { |user| user.fname.like( /^a/ ) }
|
49
91
|
# # => "select * from users where fname like 'a%'"
|
50
|
-
# fname_ends_with_a =
|
51
|
-
# user.fname.like( /a$/ )
|
52
|
-
# }
|
92
|
+
# fname_ends_with_a = User.get { |user| user.fname.like( /a$/ ) }
|
53
93
|
# # => "select * from users where fname like '%a'"
|
54
|
-
# fname_contains_a =
|
55
|
-
# user.fname.like( /a/ )
|
56
|
-
# }
|
94
|
+
# fname_contains_a = User.get { |user| user.fname.like( /a/ ) }
|
57
95
|
# # => "select * from users where fname like '%a%'"
|
58
96
|
# Please note that although we're using the Regexp operators here, these aren't
|
59
97
|
# full-fledged regexps. Only ^ and $ work for this.
|
60
98
|
#
|
61
|
-
# == Compound conditions: <tt
|
62
|
-
# invoices =
|
63
|
-
#
|
99
|
+
# == Compound conditions: <tt>&</tt> and <tt>|</tt>
|
100
|
+
# invoices = Invoice.get { |inv|
|
101
|
+
# inv.hours.equals( 40 ) & inv.rate.equals( 50 )
|
64
102
|
# }
|
65
103
|
# # => "select * from invoices where (hours = 40 and rate = 50)"
|
66
|
-
# client99 =
|
67
|
-
# invoices =
|
68
|
-
#
|
69
|
-
#
|
70
|
-
# inv.client.equals( client99 ) )
|
104
|
+
# client99 = Client[99]
|
105
|
+
# invoices = Invoice.get { |inv|
|
106
|
+
# inv.hours.equals( 40 ) | inv.rate.equals( 50 ) |
|
107
|
+
# inv.client.equals( client99 )
|
71
108
|
# }
|
72
109
|
# # => "select * from invoices where (hours = 40 or rate = 50 or client = 99)"
|
73
|
-
# Note that both compound operators can
|
74
|
-
# be nested:
|
110
|
+
# Note that both compound operators can be nested:
|
75
111
|
# invoices = object_store.getInvoices { |inv|
|
76
|
-
#
|
77
|
-
#
|
78
|
-
# inv.client.equals( client99 ) ) )
|
112
|
+
# inv.hours.equals( 40 ) &
|
113
|
+
# ( inv.rate.equals( 50 ) | inv.client.equals( client99 ) )
|
79
114
|
# }
|
80
115
|
# # => "select * from invoices where (hours = 40 and
|
81
116
|
# # (rate = 50 or client = 99))"
|
82
117
|
#
|
83
118
|
# == Negation: +not+
|
84
|
-
# invoices =
|
119
|
+
# invoices = Invoice.get { |inv| inv.rate.equals( 50 ).not }
|
85
120
|
# # => "select * from invoices where rate != 50"
|
121
|
+
# This can be used directly against boolean and nil comparisons, too.
|
122
|
+
# not_administrators = User.get { |u| u.administrator.not }
|
123
|
+
# # => "select * from users where administrator != 1"
|
124
|
+
# has_email = User.get { |u| u.email.nil?.not }
|
125
|
+
# # => "select * from users where email is not null"
|
126
|
+
#
|
127
|
+
# = Query caching via subset matching
|
128
|
+
# Lafcadio caches every query, and optimizes based on a simple subset
|
129
|
+
# calculation. For example, if you run these statements:
|
130
|
+
# User.get { |u| u.lname.equals( 'Smith' ) }
|
131
|
+
# User.get { |u| u.lname.equals( 'Smith' ) & u.fname.like( /John/ ) }
|
132
|
+
# User.get { |u| u.lname.equals( 'Smith' ) & u.email.like( /hotmail/ ) }
|
133
|
+
# Lafcadio can tell that the 2nd and 3rd queries are subsets of the first. So
|
134
|
+
# these three statements will result in one database call, for the first
|
135
|
+
# statement: The 2nd and 3rd statements will be handled entirely in Ruby. The
|
136
|
+
# result is less database calls with no extra work for the programmer.
|
86
137
|
|
87
138
|
require 'delegate'
|
88
139
|
|
89
140
|
module Lafcadio
|
90
141
|
class Query
|
91
|
-
def self.And( *conditions )
|
142
|
+
def self.And( *conditions ) #:nodoc:
|
143
|
+
CompoundCondition.new( *conditions )
|
144
|
+
end
|
92
145
|
|
146
|
+
# Infers a query from a block. The first required argument is the domain
|
147
|
+
# class. Other optional arguments should be passed in hash form:
|
148
|
+
# [:order_by] An array of fields to order the results by.
|
149
|
+
# [:order_by_order] Possible values are Query::ASC or Query::DESC. Defaults
|
150
|
+
# to Query::DESC.
|
151
|
+
# qry = Query.infer( User ) { |u| u.lname.equals( 'Hwang' ) }
|
152
|
+
# qry.to_sql # => "select * from users where users.lname = 'Hwang'"
|
153
|
+
# qry = Query.infer(
|
154
|
+
# SKU,
|
155
|
+
# :order_by => [ :standardPrice, :salePrice ],
|
156
|
+
# :order_by_order => Query::DESC
|
157
|
+
# ) { |s| s.sku.nil? }
|
158
|
+
# qry.to_sql # => "select * from skus where skus.sku is null order by
|
159
|
+
# standardPrice, salePrice desc"
|
93
160
|
def self.infer( *args, &action )
|
94
161
|
inferrer = Query::Inferrer.new( *args ) { |obj| action.call( obj ) }
|
95
162
|
inferrer.execute
|
96
163
|
end
|
97
164
|
|
98
|
-
def self.Or( *conditions )
|
165
|
+
def self.Or( *conditions ) #:nodoc:
|
99
166
|
conditions << CompoundCondition::OR
|
100
167
|
CompoundCondition.new( *conditions)
|
101
168
|
end
|
@@ -103,27 +170,34 @@ module Lafcadio
|
|
103
170
|
ASC = 1
|
104
171
|
DESC = 2
|
105
172
|
|
106
|
-
attr_reader :domain_class, :condition
|
107
|
-
attr_accessor :order_by, :order_by_order
|
173
|
+
attr_reader :domain_class, :condition, :limit
|
174
|
+
attr_accessor :order_by, :order_by_order
|
108
175
|
|
109
|
-
def initialize(domain_class, pk_id_or_condition = nil, opts = {} )
|
176
|
+
def initialize(domain_class, pk_id_or_condition = nil, opts = {} ) #:nodoc:
|
110
177
|
@domain_class, @opts = domain_class, opts
|
111
178
|
( @condition, @order_by, @limit ) = [ nil, nil, nil ]
|
112
179
|
if pk_id_or_condition
|
113
|
-
if pk_id_or_condition.
|
180
|
+
if pk_id_or_condition.is_a?( Condition )
|
114
181
|
@condition = pk_id_or_condition
|
115
182
|
else
|
116
183
|
@condition = Query::Equals.new(
|
117
|
-
|
184
|
+
:pk_id, pk_id_or_condition, domain_class
|
118
185
|
)
|
119
186
|
end
|
120
187
|
end
|
121
188
|
@order_by_order = ASC
|
122
189
|
end
|
123
190
|
|
191
|
+
# Returns a new query representing the condition of the current query and
|
192
|
+
# the new inferred query.
|
193
|
+
# qry = Query.infer( User ) { |u| u.lname.equals( 'Hwang' ) }
|
194
|
+
# qry.to_sql # => "select * from users where users.lname = 'Hwang'"
|
195
|
+
# qry = qry.and { |u| u.fname.equals( 'Francis' ) }
|
196
|
+
# qry.to_sql # => "select * from users where (users.lname = 'Hwang' and
|
197
|
+
# users.fname = 'Francis')"
|
124
198
|
def and( &action ); compound( CompoundCondition::AND, action ); end
|
125
199
|
|
126
|
-
def collect( coll )
|
200
|
+
def collect( coll ) #:nodoc:
|
127
201
|
if @opts[:group_functions] == [:count]
|
128
202
|
[ result_row( [coll.size] ) ]
|
129
203
|
else
|
@@ -131,55 +205,89 @@ module Lafcadio
|
|
131
205
|
end
|
132
206
|
end
|
133
207
|
|
134
|
-
def compound( comp_type, action )
|
208
|
+
def compound( comp_type, action ) #:nodoc:
|
135
209
|
rquery = Query.infer( @domain_class ) { |dobj| action.call( dobj ) }
|
136
|
-
|
137
|
-
|
138
|
-
|
210
|
+
q = Query::CompoundCondition.new(
|
211
|
+
@condition, rquery.condition, comp_type
|
212
|
+
).query
|
213
|
+
[ :order_by, :order_by_order, :limit ].each do |attr|
|
214
|
+
q.send( attr.to_s + '=', self.send( attr ) )
|
215
|
+
end
|
216
|
+
q
|
139
217
|
end
|
140
218
|
|
141
|
-
def
|
219
|
+
def dobj_satisfies?( dobj ) #:nodoc:
|
220
|
+
@condition.nil? or @condition.dobj_satisfies?( dobj )
|
221
|
+
end
|
142
222
|
|
143
|
-
def
|
223
|
+
def eql?( other ) #:nodoc:
|
224
|
+
other.is_a?( Query ) && other.to_sql == to_sql
|
225
|
+
end
|
226
|
+
|
227
|
+
def fields #:nodoc:
|
228
|
+
@opts[:group_functions] == [:count] ? 'count(*)' : '*'
|
229
|
+
end
|
144
230
|
|
145
|
-
def hash
|
231
|
+
def hash #:nodoc:
|
232
|
+
to_sql.hash
|
233
|
+
end
|
146
234
|
|
147
|
-
def
|
235
|
+
def implies?( other_query ) #:nodoc:
|
236
|
+
if other_query == self
|
237
|
+
true
|
238
|
+
elsif @domain_class == other_query.domain_class
|
239
|
+
if other_query.condition.nil? and !self.condition.nil?
|
240
|
+
true
|
241
|
+
else
|
242
|
+
self.condition and self.condition.implies?( other_query.condition )
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
def limit=( limit )
|
248
|
+
@limit = limit.is_a?( Fixnum ) ? 0..limit-1 : limit
|
249
|
+
end
|
250
|
+
|
251
|
+
def limit_clause #:nodoc:
|
148
252
|
"limit #{ @limit.begin }, #{ @limit.end - @limit.begin + 1 }" if @limit
|
149
253
|
end
|
150
254
|
|
151
|
-
|
152
|
-
|
255
|
+
# Returns a new query representing the condition of the current query and
|
256
|
+
# the new inferred query.
|
257
|
+
# qry = Query.infer( User ) { |u| u.lname.equals( 'Hwang' ) }
|
258
|
+
# qry.to_sql # => "select * from users where users.lname = 'Hwang'"
|
259
|
+
# qry = qry.or { |u| u.fname.equals( 'Francis' ) }
|
260
|
+
# qry.to_sql # => "select * from users where (users.lname = 'Hwang' or
|
261
|
+
# users.fname = 'Francis')"
|
153
262
|
def or( &action ); compound( CompoundCondition::OR, action ); end
|
154
263
|
|
155
|
-
def order_clause
|
264
|
+
def order_clause #:nodoc:
|
156
265
|
if @order_by
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
}.join( ', ' )
|
161
|
-
else
|
162
|
-
field_str = @domain_class.get_field( @order_by ).db_field_name
|
163
|
-
end
|
266
|
+
field_str = @order_by.map { |f_name|
|
267
|
+
@domain_class.field( f_name.to_s ).db_field_name
|
268
|
+
}.join( ', ' )
|
164
269
|
clause = "order by #{ field_str } "
|
165
270
|
clause += @order_by_order == ASC ? 'asc' : 'desc'
|
166
271
|
clause
|
167
272
|
end
|
168
273
|
end
|
169
274
|
|
170
|
-
def
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
275
|
+
def order_and_limit_collection( objects )
|
276
|
+
objects = objects.sort_by { |dobj|
|
277
|
+
if order_by.nil?
|
278
|
+
dobj.pk_id
|
279
|
+
elsif order_by.is_a?( Array )
|
280
|
+
order_by.map { |field_name| dobj.send( field_name ) }
|
176
281
|
else
|
177
|
-
|
282
|
+
dobj.send order_by
|
178
283
|
end
|
179
|
-
|
284
|
+
}
|
285
|
+
objects.reverse! if order_by_order == Query::DESC
|
286
|
+
objects = objects[limit] if limit
|
287
|
+
objects
|
180
288
|
end
|
181
289
|
|
182
|
-
def result_row( row )
|
290
|
+
def result_row( row ) #:nodoc:
|
183
291
|
if @opts[:group_functions] == [:count]
|
184
292
|
{ :count => row.first }
|
185
293
|
else
|
@@ -187,11 +295,11 @@ module Lafcadio
|
|
187
295
|
end
|
188
296
|
end
|
189
297
|
|
190
|
-
def sql_primary_key_field(domain_class)
|
298
|
+
def sql_primary_key_field(domain_class) #:nodoc:
|
191
299
|
"#{ domain_class.table_name }.#{ domain_class.sql_primary_key_name }"
|
192
300
|
end
|
193
301
|
|
194
|
-
def tables
|
302
|
+
def tables #:nodoc:
|
195
303
|
concrete_classes = domain_class.self_and_concrete_superclasses.reverse
|
196
304
|
table_names = concrete_classes.collect { |domain_class|
|
197
305
|
domain_class.table_name
|
@@ -207,7 +315,7 @@ module Lafcadio
|
|
207
315
|
clauses.join ' '
|
208
316
|
end
|
209
317
|
|
210
|
-
def where_clause
|
318
|
+
def where_clause #:nodoc:
|
211
319
|
concrete_classes = domain_class.self_and_concrete_superclasses.reverse
|
212
320
|
where_clauses = []
|
213
321
|
concrete_classes.each_with_index { |domain_class, i|
|
@@ -219,7 +327,7 @@ module Lafcadio
|
|
219
327
|
where_clauses << @condition.to_sql if @condition
|
220
328
|
end
|
221
329
|
}
|
222
|
-
where_clauses.
|
330
|
+
!where_clauses.empty? ? 'where ' + where_clauses.join( ' and ' ) : nil
|
223
331
|
end
|
224
332
|
|
225
333
|
class Condition #:nodoc:
|
@@ -230,16 +338,13 @@ module Lafcadio
|
|
230
338
|
attr_reader :domain_class
|
231
339
|
|
232
340
|
def initialize(fieldName, searchTerm, domain_class)
|
233
|
-
@fieldName =
|
234
|
-
|
235
|
-
unless @searchTerm.
|
341
|
+
@fieldName, @searchTerm, @domain_class =
|
342
|
+
fieldName, searchTerm, domain_class
|
343
|
+
unless @searchTerm.is_a?( self.class.search_term_type )
|
236
344
|
raise "Incorrect searchTerm type #{ searchTerm.class }"
|
237
345
|
end
|
238
|
-
@domain_class
|
239
|
-
|
240
|
-
unless @domain_class <= DomainObject
|
241
|
-
raise "Incorrect object type #{ @domain_class.to_s }"
|
242
|
-
end
|
346
|
+
if @domain_class and !( @domain_class < DomainObject )
|
347
|
+
raise "Incorrect object type #{ @domain_class.to_s }"
|
243
348
|
end
|
244
349
|
end
|
245
350
|
|
@@ -254,22 +359,28 @@ module Lafcadio
|
|
254
359
|
)
|
255
360
|
end
|
256
361
|
|
257
|
-
def db_field_name;
|
362
|
+
def db_field_name; field.db_column; end
|
258
363
|
|
259
364
|
def eql?( other_cond )
|
260
365
|
other_cond.is_a?( Condition ) and other_cond.to_sql == to_sql
|
261
366
|
end
|
262
367
|
|
263
|
-
def
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
368
|
+
def field
|
369
|
+
f = @domain_class.field @fieldName.to_s
|
370
|
+
f or raise(
|
371
|
+
MissingError,
|
372
|
+
"Couldn't find field \"#{ @fieldName }\" in " + @domain_class.name +
|
373
|
+
" domain class",
|
374
|
+
caller
|
375
|
+
)
|
269
376
|
end
|
377
|
+
|
378
|
+
def not; Query::Not.new( self ); end
|
270
379
|
|
271
380
|
def primary_key_field?; 'pk_id' == @fieldName; end
|
272
381
|
|
382
|
+
def query; Query.new( @domain_class, self ); end
|
383
|
+
|
273
384
|
def to_condition; self; end
|
274
385
|
end
|
275
386
|
|
@@ -298,17 +409,7 @@ module Lafcadio
|
|
298
409
|
@compareType = compareType
|
299
410
|
end
|
300
411
|
|
301
|
-
def
|
302
|
-
if ( get_field.kind_of?( DomainObjectField ) &&
|
303
|
-
!@searchTerm.respond_to?( :pk_id ) )
|
304
|
-
search_val = @searchTerm.to_s
|
305
|
-
else
|
306
|
-
search_val = get_field.value_for_sql( @searchTerm ).to_s
|
307
|
-
end
|
308
|
-
"#{ db_field_name } #{ @@comparators[@compareType] } " + search_val
|
309
|
-
end
|
310
|
-
|
311
|
-
def object_meets(anObj)
|
412
|
+
def dobj_satisfies?(anObj)
|
312
413
|
value = anObj.send @fieldName
|
313
414
|
value = value.pk_id if value.class <= DomainObject
|
314
415
|
if value
|
@@ -317,6 +418,16 @@ module Lafcadio
|
|
317
418
|
false
|
318
419
|
end
|
319
420
|
end
|
421
|
+
|
422
|
+
def to_sql
|
423
|
+
if ( field.kind_of?( DomainObjectField ) &&
|
424
|
+
!@searchTerm.respond_to?( :pk_id ) )
|
425
|
+
search_val = @searchTerm.to_s
|
426
|
+
else
|
427
|
+
search_val = field.value_for_sql( @searchTerm ).to_s
|
428
|
+
end
|
429
|
+
"#{ db_field_name } #{ @@comparators[@compareType] } #{ search_val }"
|
430
|
+
end
|
320
431
|
end
|
321
432
|
|
322
433
|
class CompoundCondition < Condition #:nodoc:
|
@@ -324,7 +435,7 @@ module Lafcadio
|
|
324
435
|
OR = 2
|
325
436
|
|
326
437
|
def initialize( *args )
|
327
|
-
if( [ AND, OR ].
|
438
|
+
if( [ AND, OR ].include?( args.last ) )
|
328
439
|
@compound_type = args.last
|
329
440
|
args.pop
|
330
441
|
else
|
@@ -336,6 +447,18 @@ module Lafcadio
|
|
336
447
|
@domain_class = @conditions[0].domain_class
|
337
448
|
end
|
338
449
|
|
450
|
+
def dobj_satisfies?(anObj)
|
451
|
+
if @compound_type == AND
|
452
|
+
@conditions.inject( true ) { |result, cond|
|
453
|
+
result && cond.dobj_satisfies?( anObj )
|
454
|
+
}
|
455
|
+
else
|
456
|
+
@conditions.inject( false ) { |result, cond|
|
457
|
+
result || cond.dobj_satisfies?( anObj )
|
458
|
+
}
|
459
|
+
end
|
460
|
+
end
|
461
|
+
|
339
462
|
def implied_by?( other_condition )
|
340
463
|
@compound_type == OR && @conditions.any? { |cond|
|
341
464
|
cond.implies?( other_condition )
|
@@ -345,27 +468,15 @@ module Lafcadio
|
|
345
468
|
def implies?( other_condition )
|
346
469
|
super( other_condition ) or (
|
347
470
|
@compound_type == AND and @conditions.any? { |cond|
|
348
|
-
cond.implies?
|
471
|
+
cond.implies? other_condition
|
349
472
|
}
|
350
473
|
) or (
|
351
474
|
@compound_type == OR and @conditions.all? { |cond|
|
352
|
-
cond.implies?
|
475
|
+
cond.implies? other_condition
|
353
476
|
}
|
354
477
|
)
|
355
478
|
end
|
356
479
|
|
357
|
-
def object_meets(anObj)
|
358
|
-
if @compound_type == AND
|
359
|
-
@conditions.inject( true ) { |result, cond|
|
360
|
-
result && cond.object_meets( anObj )
|
361
|
-
}
|
362
|
-
else
|
363
|
-
@conditions.inject( false ) { |result, cond|
|
364
|
-
result || cond.object_meets( anObj )
|
365
|
-
}
|
366
|
-
end
|
367
|
-
end
|
368
|
-
|
369
480
|
def to_sql
|
370
481
|
booleanString = @compound_type == AND ? 'and' : 'or'
|
371
482
|
subSqlStrings = @conditions.collect { |cond| cond.to_sql }
|
@@ -386,10 +497,9 @@ module Lafcadio
|
|
386
497
|
|
387
498
|
def method_missing( methId, *args )
|
388
499
|
fieldName = methId.id2name
|
389
|
-
|
390
|
-
classField = self.domain_class.get_field( fieldName )
|
500
|
+
if ( classField = self.domain_class.field( fieldName ) )
|
391
501
|
ObjectFieldImpostor.new( self, classField )
|
392
|
-
|
502
|
+
else
|
393
503
|
super( methId, *args )
|
394
504
|
end
|
395
505
|
end
|
@@ -410,10 +520,18 @@ module Lafcadio
|
|
410
520
|
end
|
411
521
|
|
412
522
|
class Equals < Condition #:nodoc:
|
523
|
+
def dobj_satisfies?(anObj)
|
524
|
+
if @searchTerm.is_a?( ObjectField )
|
525
|
+
compare_value = anObj.send @searchTerm.name
|
526
|
+
else
|
527
|
+
compare_value = @searchTerm
|
528
|
+
end
|
529
|
+
compare_value == anObj.send( @fieldName )
|
530
|
+
end
|
531
|
+
|
413
532
|
def r_val_string
|
414
|
-
|
415
|
-
|
416
|
-
@searchTerm.db_table_and_field_name
|
533
|
+
if @searchTerm.is_a?( ObjectField )
|
534
|
+
@searchTerm.db_column
|
417
535
|
else
|
418
536
|
begin
|
419
537
|
field.value_for_sql( @searchTerm ).to_s
|
@@ -428,38 +546,22 @@ module Lafcadio
|
|
428
546
|
end
|
429
547
|
end
|
430
548
|
|
431
|
-
def object_meets(anObj)
|
432
|
-
if @searchTerm.class <= ObjectField
|
433
|
-
compare_value = anObj.send( @searchTerm.name )
|
434
|
-
else
|
435
|
-
compare_value = @searchTerm
|
436
|
-
end
|
437
|
-
compare_value == anObj.send( @fieldName )
|
438
|
-
end
|
439
|
-
|
440
549
|
def to_sql
|
441
550
|
sql = "#{ db_field_name } "
|
442
|
-
|
443
|
-
sql += "= " + r_val_string
|
444
|
-
else
|
445
|
-
sql += "is null"
|
446
|
-
end
|
551
|
+
sql += ( !@searchTerm.nil? ? "= #{ r_val_string }" : "is null" )
|
447
552
|
sql
|
448
553
|
end
|
449
554
|
end
|
450
555
|
|
451
556
|
class In < Condition #:nodoc:
|
452
|
-
def self.search_term_type
|
453
|
-
Array
|
454
|
-
end
|
557
|
+
def self.search_term_type; Array; end
|
455
558
|
|
456
|
-
def
|
457
|
-
|
458
|
-
@searchTerm.index(value) != nil
|
559
|
+
def dobj_satisfies?(anObj)
|
560
|
+
@searchTerm.include?( anObj.send( @fieldName ) )
|
459
561
|
end
|
460
562
|
|
461
563
|
def to_sql
|
462
|
-
if
|
564
|
+
if field.is_a?( StringField )
|
463
565
|
quoted = @searchTerm.map do |str| "'#{ str }'"; end
|
464
566
|
end_clause = quoted.join ', '
|
465
567
|
else
|
@@ -469,14 +571,17 @@ module Lafcadio
|
|
469
571
|
end
|
470
572
|
end
|
471
573
|
|
472
|
-
class Include < CompoundCondition
|
574
|
+
class Include < CompoundCondition #:nodoc:
|
473
575
|
def initialize( field_name, search_term, domain_class )
|
474
|
-
begin_cond = Like.new(
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
576
|
+
begin_cond = Like.new(
|
577
|
+
field_name, search_term + ',', domain_class, Like::POST_ONLY
|
578
|
+
)
|
579
|
+
mid_cond = Like.new(
|
580
|
+
field_name, ',' + search_term + ',', domain_class
|
581
|
+
)
|
582
|
+
end_cond = Like.new(
|
583
|
+
field_name, ',' + search_term, domain_class, Like::PRE_ONLY
|
584
|
+
)
|
480
585
|
only_cond = Equals.new( field_name, search_term, domain_class )
|
481
586
|
super( begin_cond, mid_cond, end_cond, only_cond, OR )
|
482
587
|
end
|
@@ -488,16 +593,18 @@ module Lafcadio
|
|
488
593
|
unless args.size == 1
|
489
594
|
h = args.last
|
490
595
|
@order_by = h[:order_by]
|
491
|
-
@order_by_order = h[:order_by_order]
|
596
|
+
@order_by_order = ( h[:order_by_order] or ASC )
|
597
|
+
@limit = h[:limit]
|
492
598
|
end
|
493
599
|
end
|
494
600
|
|
495
601
|
def execute
|
496
|
-
impostor = DomainObjectImpostor.impostor
|
602
|
+
impostor = DomainObjectImpostor.impostor @domain_class
|
497
603
|
condition = @action.call( impostor ).to_condition
|
498
604
|
query = Query.new( @domain_class, condition )
|
499
605
|
query.order_by = @order_by
|
500
606
|
query.order_by_order = @order_by_order
|
607
|
+
query.limit = @limit
|
501
608
|
query
|
502
609
|
end
|
503
610
|
end
|
@@ -508,12 +615,40 @@ module Lafcadio
|
|
508
615
|
POST_ONLY = 3
|
509
616
|
|
510
617
|
def initialize(
|
511
|
-
|
618
|
+
fieldName, searchTerm, domain_class, matchType = PRE_AND_POST
|
619
|
+
)
|
620
|
+
if searchTerm.is_a? Regexp
|
621
|
+
searchTerm = process_regexp searchTerm
|
622
|
+
else
|
623
|
+
@matchType = matchType
|
624
|
+
end
|
512
625
|
super fieldName, searchTerm, domain_class
|
513
|
-
@matchType = matchType
|
514
626
|
end
|
515
627
|
|
516
|
-
def
|
628
|
+
def dobj_satisfies?(anObj)
|
629
|
+
value = anObj.send @fieldName
|
630
|
+
value = value.pk_id.to_s if value.respond_to?( :pk_id )
|
631
|
+
if value.is_a?( Array )
|
632
|
+
value.include? @searchTerm
|
633
|
+
else
|
634
|
+
!regexp.match( value ).nil?
|
635
|
+
end
|
636
|
+
end
|
637
|
+
|
638
|
+
def process_regexp( searchTerm )
|
639
|
+
if searchTerm.source =~ /^\^(.*)/
|
640
|
+
@matchType = Query::Like::POST_ONLY
|
641
|
+
$1
|
642
|
+
elsif searchTerm.source =~ /(.*)\$$/
|
643
|
+
@matchType = Query::Like::PRE_ONLY
|
644
|
+
$1
|
645
|
+
else
|
646
|
+
@matchType = Query::Like::PRE_AND_POST
|
647
|
+
searchTerm.source
|
648
|
+
end
|
649
|
+
end
|
650
|
+
|
651
|
+
def regexp
|
517
652
|
if @matchType == PRE_AND_POST
|
518
653
|
Regexp.new( @searchTerm, Regexp::IGNORECASE )
|
519
654
|
elsif @matchType == PRE_ONLY
|
@@ -523,18 +658,6 @@ module Lafcadio
|
|
523
658
|
end
|
524
659
|
end
|
525
660
|
|
526
|
-
def object_meets(anObj)
|
527
|
-
value = anObj.send @fieldName
|
528
|
-
if value.class <= DomainObject || value.class == DomainObjectProxy
|
529
|
-
value = value.pk_id.to_s
|
530
|
-
end
|
531
|
-
if value.class <= Array
|
532
|
-
(value.index(@searchTerm) != nil)
|
533
|
-
else
|
534
|
-
get_regexp.match(value) != nil
|
535
|
-
end
|
536
|
-
end
|
537
|
-
|
538
661
|
def to_sql
|
539
662
|
withWildcards = @searchTerm
|
540
663
|
if @matchType == PRE_AND_POST
|
@@ -558,14 +681,14 @@ module Lafcadio
|
|
558
681
|
|
559
682
|
def collect( coll )
|
560
683
|
max = coll.inject( nil ) { |max, d_obj|
|
561
|
-
a_value = d_obj.send
|
684
|
+
a_value = d_obj.send @field_name
|
562
685
|
( max.nil? || a_value > max ) ? a_value : max
|
563
686
|
}
|
564
687
|
[ result_row( [max] ) ]
|
565
688
|
end
|
566
689
|
|
567
690
|
def fields
|
568
|
-
"max(#{ @domain_class.
|
691
|
+
"max(#{ @domain_class.field( @field_name ).db_field_name })"
|
569
692
|
end
|
570
693
|
|
571
694
|
def result_row( row ); { :max => row.first }; end
|
@@ -576,8 +699,8 @@ module Lafcadio
|
|
576
699
|
@unCondition = unCondition
|
577
700
|
end
|
578
701
|
|
579
|
-
def
|
580
|
-
!@unCondition.
|
702
|
+
def dobj_satisfies?(obj)
|
703
|
+
!@unCondition.dobj_satisfies?(obj)
|
581
704
|
end
|
582
705
|
|
583
706
|
def domain_class; @unCondition.domain_class; end
|
@@ -598,42 +721,40 @@ module Lafcadio
|
|
598
721
|
|
599
722
|
attr_reader :class_field
|
600
723
|
|
601
|
-
def initialize( domainObjectImpostor,
|
724
|
+
def initialize( domainObjectImpostor, class_field )
|
602
725
|
@domainObjectImpostor = domainObjectImpostor
|
603
|
-
|
604
|
-
|
605
|
-
else
|
606
|
-
@class_field = class_field_or_name
|
607
|
-
@field_name = class_field_or_name.name
|
608
|
-
end
|
726
|
+
@class_field = class_field
|
727
|
+
@field_name = class_field.name
|
609
728
|
end
|
610
729
|
|
730
|
+
def &( condition ); Query.And( to_condition, condition ); end
|
731
|
+
|
732
|
+
def |( condition ); Query.Or( to_condition, condition ); end
|
733
|
+
|
611
734
|
def method_missing( methId, *args )
|
612
735
|
methodName = methId.id2name
|
613
|
-
if
|
614
|
-
|
736
|
+
if self.class.comparators.keys.include?( methodName )
|
737
|
+
compare_condition( methodName, *args )
|
615
738
|
else
|
616
|
-
super
|
739
|
+
super
|
617
740
|
end
|
618
741
|
end
|
619
742
|
|
620
|
-
def
|
621
|
-
|
622
|
-
def &( condition ); Query.And( to_condition, condition ); end
|
623
|
-
|
624
|
-
def register_compare_condition( compareStr, searchTerm)
|
743
|
+
def compare_condition( compareStr, searchTerm)
|
625
744
|
compareVal = ObjectFieldImpostor.comparators[compareStr]
|
626
|
-
Compare.new( @field_name, searchTerm,
|
627
|
-
@domainObjectImpostor.domain_class, compareVal )
|
745
|
+
Compare.new( @field_name, searchTerm, domain_class, compareVal )
|
628
746
|
end
|
629
747
|
|
748
|
+
def domain_class; @domainObjectImpostor.domain_class; end
|
749
|
+
|
630
750
|
def equals( searchTerm )
|
631
|
-
Equals.new(
|
632
|
-
|
751
|
+
Equals.new(
|
752
|
+
@field_name, field_or_field_name( searchTerm ), domain_class
|
753
|
+
)
|
633
754
|
end
|
634
755
|
|
635
756
|
def field_or_field_name( search_term )
|
636
|
-
if search_term.
|
757
|
+
if search_term.is_a? ObjectFieldImpostor
|
637
758
|
search_term.class_field
|
638
759
|
else
|
639
760
|
search_term
|
@@ -641,9 +762,8 @@ module Lafcadio
|
|
641
762
|
end
|
642
763
|
|
643
764
|
def include?( search_term )
|
644
|
-
if @class_field.
|
645
|
-
Include.new( @field_name, search_term,
|
646
|
-
@domainObjectImpostor.domain_class )
|
765
|
+
if @class_field.is_a?( TextListField )
|
766
|
+
Include.new( @field_name, search_term, domain_class )
|
647
767
|
else
|
648
768
|
raise ArgumentError
|
649
769
|
end
|
@@ -651,18 +771,7 @@ module Lafcadio
|
|
651
771
|
|
652
772
|
def like( regexp )
|
653
773
|
if regexp.is_a?( Regexp )
|
654
|
-
|
655
|
-
searchTerm = $1
|
656
|
-
matchType = Query::Like::POST_ONLY
|
657
|
-
elsif regexp.source =~ /(.*)\$$/
|
658
|
-
searchTerm = $1
|
659
|
-
matchType = Query::Like::PRE_ONLY
|
660
|
-
else
|
661
|
-
searchTerm = regexp.source
|
662
|
-
matchType = Query::Like::PRE_AND_POST
|
663
|
-
end
|
664
|
-
Query::Like.new( @field_name, searchTerm,
|
665
|
-
@domainObjectImpostor.domain_class, matchType )
|
774
|
+
Query::Like.new( @field_name, regexp, domain_class )
|
666
775
|
else
|
667
776
|
raise(
|
668
777
|
ArgumentError, "#{ @field_name }#like needs to receive a Regexp",
|
@@ -672,16 +781,14 @@ module Lafcadio
|
|
672
781
|
end
|
673
782
|
|
674
783
|
def in( *searchTerms )
|
675
|
-
Query::In.new( @field_name, searchTerms,
|
676
|
-
@domainObjectImpostor.domain_class )
|
784
|
+
Query::In.new( @field_name, searchTerms, domain_class )
|
677
785
|
end
|
678
786
|
|
679
787
|
def nil?; equals( nil ); end
|
680
788
|
|
681
789
|
def to_condition
|
682
790
|
if @class_field.instance_of?( BooleanField )
|
683
|
-
Query::Equals.new( @field_name, true,
|
684
|
-
@domainObjectImpostor.domain_class )
|
791
|
+
Query::Equals.new( @field_name, true, domain_class )
|
685
792
|
else
|
686
793
|
raise
|
687
794
|
end
|