lafcadio 0.9.0 → 0.9.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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 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"
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 = object_store.getInvoices { |inv| inv.date.gt( date ) }
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 = object_store.getInvoices { |inv|
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 = object_store.getInvoices { |inv| inv.rate.lte( 25 ) }
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 = object_store.getInvoices { |inv|
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 = object_store.getInvoices { |inv| inv.hours.equals( 40 ) }
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 = object_store.getClient( 99 )
38
- # invoices = object_store.getInvoices { |inv| inv.client.equals( client ) }
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
- # first_three_invs = object_store.getInvoices { |inv| inv.pk_id.in( 1, 2, 3 ) }
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 = object_store.getUsers { |user|
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 = object_store.getUsers { |user|
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 = object_store.getUsers { |user|
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>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 ) )
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 = 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 ) )
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 take 2 or more arguments. Also, they can
74
- # be nested:
110
+ # Note that both compound operators can be nested:
75
111
  # invoices = object_store.getInvoices { |inv|
76
- # Query.And( inv.hours.equals( 40 ),
77
- # Query.Or( inv.rate.equals( 50 ),
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 = object_store.getInvoices { |inv| inv.rate.equals( 50 ).not }
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 ); CompoundCondition.new( *conditions ); end
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, :limit
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.class <= 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
- 'pk_id', pk_id_or_condition, domain_class
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
- comp_cond = Query::CompoundCondition.new( @condition, rquery.condition,
137
- comp_type )
138
- comp_cond.query
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 eql?( other ); other.class <= Query && other.to_sql == to_sql; end
219
+ def dobj_satisfies?( dobj ) #:nodoc:
220
+ @condition.nil? or @condition.dobj_satisfies?( dobj )
221
+ end
142
222
 
143
- def fields; @opts[:group_functions] == [:count] ? 'count(*)' : '*'; end
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; to_sql.hash; end
231
+ def hash #:nodoc:
232
+ to_sql.hash
233
+ end
146
234
 
147
- def limit_clause
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
- def object_meets( dobj ); @condition.object_meets( dobj ); end
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
- if @order_by.is_a? Array
158
- field_str = @order_by.map { |f_name|
159
- @domain_class.get_field( f_name.to_s ).db_field_name
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 implies?( other_query )
171
- if other_query == self
172
- true
173
- elsif @domain_class == other_query.domain_class
174
- if other_query.condition.nil? and !self.condition.nil?
175
- true
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
- self.condition and self.condition.implies?( other_query.condition )
282
+ dobj.send order_by
178
283
  end
179
- end
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.size > 0 ? 'where ' + where_clauses.join( ' and ' ) : nil
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 = fieldName
234
- @searchTerm = searchTerm
235
- unless @searchTerm.class <= self.class.search_term_type
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 = domain_class
239
- if @domain_class
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; get_field.db_table_and_field_name; end
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 get_field; @domain_class.get_field( @fieldName ); end
264
-
265
- def query; Query.new( @domain_class, self ); end
266
-
267
- def not
268
- Query::Not.new( self )
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 to_sql
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 ].index( args.last) )
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?( other_condition )
471
+ cond.implies? other_condition
349
472
  }
350
473
  ) or (
351
474
  @compound_type == OR and @conditions.all? { |cond|
352
- cond.implies?( other_condition )
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
- begin
390
- classField = self.domain_class.get_field( fieldName )
500
+ if ( classField = self.domain_class.field( fieldName ) )
391
501
  ObjectFieldImpostor.new( self, classField )
392
- rescue MissingError
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
- field = get_field
415
- if @searchTerm.class <= ObjectField
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
- unless @searchTerm.nil?
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 object_meets(anObj)
457
- value = anObj.send @fieldName
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 get_field.is_a?( StringField )
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( field_name, search_term + ',', domain_class,
475
- Like::POST_ONLY )
476
- mid_cond = Like.new( field_name, ',' + search_term + ',',
477
- domain_class )
478
- end_cond = Like.new( field_name, ',' + search_term, domain_class,
479
- Like::PRE_ONLY )
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( @domain_class )
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
- fieldName, searchTerm, domain_class, matchType = PRE_AND_POST)
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 get_regexp
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( @field_name )
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.get_field( @field_name ).db_field_name })"
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 object_meets(obj)
580
- !@unCondition.object_meets(obj)
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, class_field_or_name )
724
+ def initialize( domainObjectImpostor, class_field )
602
725
  @domainObjectImpostor = domainObjectImpostor
603
- if class_field_or_name == 'pk_id'
604
- @field_name = 'pk_id'
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 !ObjectFieldImpostor.comparators.keys.index( methodName ).nil?
614
- register_compare_condition( methodName, *args )
736
+ if self.class.comparators.keys.include?( methodName )
737
+ compare_condition( methodName, *args )
615
738
  else
616
- super( methId, *args )
739
+ super
617
740
  end
618
741
  end
619
742
 
620
- def |( condition ); Query.Or( to_condition, condition ); end
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( @field_name, field_or_field_name( searchTerm ),
632
- @domainObjectImpostor.domain_class )
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.class == ObjectFieldImpostor
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.instance_of?( TextListField )
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
- if regexp.source =~ /^\^(.*)/
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