lafcadio 0.9.2 → 0.9.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -32,7 +32,7 @@
32
32
  # qry = Query.infer(
33
33
  # SKU,
34
34
  # :order_by => [ :standardPrice, :salePrice ],
35
- # :order_by_order => Query::DESC
35
+ # :order_by_order => :desc
36
36
  # ) { |s| s.sku.nil? }
37
37
  # qry.to_sql # => "select * from skus where skus.sku is null order by
38
38
  # standardPrice, salePrice desc"
@@ -93,8 +93,10 @@
93
93
  # # => "select * from users where fname like '%a'"
94
94
  # fname_contains_a = User.get { |user| user.fname.like( /a/ ) }
95
95
  # # => "select * from users where fname like '%a%'"
96
+ # james_or_jones = User.get { |user| user.lname.like( /J..es/ ) }
97
+ # # => "select * from users where lname like 'J__es'"
96
98
  # Please note that although we're using the Regexp operators here, these aren't
97
- # full-fledged regexps. Only ^ and $ work for this.
99
+ # full-fledged regexps. Only ^, $, and . work for this.
98
100
  #
99
101
  # == Compound conditions: <tt>&</tt> and <tt>|</tt>
100
102
  # invoices = Invoice.get { |inv|
@@ -146,14 +148,13 @@ module Lafcadio
146
148
  # Infers a query from a block. The first required argument is the domain
147
149
  # class. Other optional arguments should be passed in hash form:
148
150
  # [: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
+ # [:order_by_order] Possible values are :asc or :desc. Defaults to :desc.
151
152
  # qry = Query.infer( User ) { |u| u.lname.equals( 'Hwang' ) }
152
153
  # qry.to_sql # => "select * from users where users.lname = 'Hwang'"
153
154
  # qry = Query.infer(
154
155
  # SKU,
155
156
  # :order_by => [ :standardPrice, :salePrice ],
156
- # :order_by_order => Query::DESC
157
+ # :order_by_order => :desc
157
158
  # ) { |s| s.sku.nil? }
158
159
  # qry.to_sql # => "select * from skus where skus.sku is null order by
159
160
  # standardPrice, salePrice desc"
@@ -163,29 +164,25 @@ module Lafcadio
163
164
  end
164
165
 
165
166
  def self.Or( *conditions ) #:nodoc:
166
- conditions << CompoundCondition::OR
167
+ conditions << :or
167
168
  CompoundCondition.new( *conditions)
168
169
  end
169
170
 
170
- ASC = 1
171
- DESC = 2
171
+ attr_reader :domain_class, :condition, :include, :limit, :order_by
172
+ attr_accessor :order_by_order
172
173
 
173
- attr_reader :domain_class, :condition, :limit
174
- attr_accessor :order_by, :order_by_order
175
-
176
- def initialize(domain_class, pk_id_or_condition = nil, opts = {} ) #:nodoc:
174
+ def initialize(domain_class, opts = {} ) #:nodoc:
177
175
  @domain_class, @opts = domain_class, opts
178
176
  ( @condition, @order_by, @limit ) = [ nil, nil, nil ]
179
- if pk_id_or_condition
180
- if pk_id_or_condition.is_a?( Condition )
181
- @condition = pk_id_or_condition
182
- else
183
- @condition = Query::Equals.new(
184
- :pk_id, pk_id_or_condition, domain_class
185
- )
186
- end
177
+ if ( cond = opts[:condition] )
178
+ @condition = cond
179
+ elsif ( pk_id = opts[:pk_id] )
180
+ @condition = Query::Equals.new( :pk_id, pk_id, domain_class )
187
181
  end
188
- @order_by_order = ASC
182
+ if ( @include = opts[:include] )
183
+ @include = [ @include ] unless @include.is_a?( Array )
184
+ end
185
+ @order_by_order = :asc
189
186
  end
190
187
 
191
188
  # Returns a new query representing the condition of the current query and
@@ -195,7 +192,7 @@ module Lafcadio
195
192
  # qry = qry.and { |u| u.fname.equals( 'Francis' ) }
196
193
  # qry.to_sql # => "select * from users where (users.lname = 'Hwang' and
197
194
  # users.fname = 'Francis')"
198
- def and( &action ); compound( CompoundCondition::AND, action ); end
195
+ def and( &action ); compound( :and, action ); end
199
196
 
200
197
  def collect( coll ) #:nodoc:
201
198
  if @opts[:group_functions] == [:count]
@@ -248,10 +245,21 @@ module Lafcadio
248
245
  @limit = limit.is_a?( Fixnum ) ? 0..limit-1 : limit
249
246
  end
250
247
 
251
- def limit_clause #:nodoc:
252
- "limit #{ @limit.begin }, #{ @limit.end - @limit.begin + 1 }" if @limit
248
+ def limit_clause( db ) #:nodoc:
249
+ if @limit
250
+ case db
251
+ when 'Mysql'
252
+ "limit #{ @limit.begin }, #{ @limit.end - @limit.begin + 1 }"
253
+ when 'Pg'
254
+ limit_clause = "limit #{ @limit.end - @limit.begin + 1 }"
255
+ limit_clause += " offset #{ @limit.begin }" if @limit.begin > 0
256
+ limit_clause
257
+ end
258
+ end
253
259
  end
254
260
 
261
+ def one_pk_id?; @condition and @condition.one_pk_id?; end
262
+
255
263
  # Returns a new query representing the condition of the current query and
256
264
  # the new inferred query.
257
265
  # qry = Query.infer( User ) { |u| u.lname.equals( 'Hwang' ) }
@@ -259,7 +267,11 @@ module Lafcadio
259
267
  # qry = qry.or { |u| u.fname.equals( 'Francis' ) }
260
268
  # qry.to_sql # => "select * from users where (users.lname = 'Hwang' or
261
269
  # users.fname = 'Francis')"
262
- def or( &action ); compound( CompoundCondition::OR, action ); end
270
+ def or( &action ); compound( :or, action ); end
271
+
272
+ def order_by=( ob )
273
+ @order_by = ( ob.is_a?( Array ) ? ob.map { |f| f.to_s } : ob.to_s ) if ob
274
+ end
263
275
 
264
276
  def order_clause #:nodoc:
265
277
  if @order_by
@@ -267,7 +279,7 @@ module Lafcadio
267
279
  @domain_class.field( f_name.to_s ).db_field_name
268
280
  }.join( ', ' )
269
281
  clause = "order by #{ field_str } "
270
- clause += @order_by_order == ASC ? 'asc' : 'desc'
282
+ clause += @order_by_order == :asc ? 'asc' : 'desc'
271
283
  clause
272
284
  end
273
285
  end
@@ -282,7 +294,7 @@ module Lafcadio
282
294
  dobj.send order_by
283
295
  end
284
296
  }
285
- objects.reverse! if order_by_order == Query::DESC
297
+ objects.reverse! if order_by_order == :desc
286
298
  objects = objects[limit] if limit
287
299
  objects
288
300
  end
@@ -301,32 +313,38 @@ module Lafcadio
301
313
 
302
314
  def tables #:nodoc:
303
315
  concrete_classes = domain_class.self_and_concrete_superclasses.reverse
304
- table_names = concrete_classes.collect { |domain_class|
305
- domain_class.table_name
306
- }
307
- table_names.join( ', ' )
316
+ sql = ''
317
+ dclass = nil
318
+ until concrete_classes.empty?
319
+ prev_dclass = dclass
320
+ dclass = concrete_classes.shift
321
+ if sql == ''
322
+ sql = dclass.table_name
323
+ else
324
+ sql += " inner join #{ dclass.table_name } on #{ sql_primary_key_field( prev_dclass ) } = #{ sql_primary_key_field( dclass ) }"
325
+ end
326
+ end
327
+ if @include
328
+ @include.each do |include_sym|
329
+ field = dclass.field include_sym
330
+ included_dclass = field.linked_type
331
+ sql += " left outer join #{ included_dclass.table_name } on #{ dclass.table_name }.#{ field.db_field_name } = #{ sql_primary_key_field( included_dclass ) }"
332
+ end
333
+ end
334
+ sql
308
335
  end
309
336
 
310
- def to_sql
337
+ def to_sql( db = 'Mysql' )
311
338
  clauses = [ "select #{ fields }", "from #{ tables }" ]
312
339
  clauses << where_clause if where_clause
313
340
  clauses << order_clause if order_clause
314
- clauses << limit_clause if limit_clause
341
+ clauses << limit_clause( db ) if limit_clause( db )
315
342
  clauses.join ' '
316
343
  end
317
344
 
318
345
  def where_clause #:nodoc:
319
- concrete_classes = domain_class.self_and_concrete_superclasses.reverse
320
346
  where_clauses = []
321
- concrete_classes.each_with_index { |domain_class, i|
322
- if i < concrete_classes.size - 1
323
- join_clause = sql_primary_key_field( domain_class ) + ' = ' +
324
- sql_primary_key_field( concrete_classes[i+1] )
325
- where_clauses << join_clause
326
- else
327
- where_clauses << @condition.to_sql if @condition
328
- end
329
- }
347
+ where_clauses << @condition.to_sql if @condition
330
348
  !where_clauses.empty? ? 'where ' + where_clauses.join( ' and ' ) : nil
331
349
  end
332
350
 
@@ -376,32 +394,24 @@ module Lafcadio
376
394
  end
377
395
 
378
396
  def not; Query::Not.new( self ); end
397
+
398
+ def one_pk_id?; self.is_a?( Equals ) and primary_key_field?; end
379
399
 
380
400
  def primary_key_field?; 'pk_id' == @fieldName; end
381
401
 
382
- def query; Query.new( @domain_class, self ); end
402
+ def query; Query.new( @domain_class, :condition => self ); end
383
403
 
384
404
  def to_condition; self; end
385
405
  end
386
406
 
387
407
  class Compare < Condition #:nodoc:
388
- LESS_THAN = 1
389
- LESS_THAN_OR_EQUAL = 2
390
- GREATER_THAN_OR_EQUAL = 3
391
- GREATER_THAN = 4
392
-
393
- @@comparators = {
394
- LESS_THAN => '<',
395
- LESS_THAN_OR_EQUAL => '<=',
396
- GREATER_THAN_OR_EQUAL => '>=',
397
- GREATER_THAN => '>'
398
- }
408
+ @@comparators = { :lt => '<', :lte => '<=', :gte => '>=', :gt => '>' }
399
409
 
400
410
  @@mockComparators = {
401
- LESS_THAN => Proc.new { |d1, d2| d1 < d2 },
402
- LESS_THAN_OR_EQUAL => Proc.new { |d1, d2| d1 <= d2 },
403
- GREATER_THAN_OR_EQUAL => Proc.new { |d1, d2| d1 >= d2 },
404
- GREATER_THAN => Proc.new { |d1, d2| d1 > d2 }
411
+ :lt => Proc.new { |d1, d2| d1 < d2 },
412
+ :lte => Proc.new { |d1, d2| d1 <= d2 },
413
+ :gte => Proc.new { |d1, d2| d1 >= d2 },
414
+ :gt => Proc.new { |d1, d2| d1 > d2 }
405
415
  }
406
416
 
407
417
  def initialize(fieldName, searchTerm, domain_class, compareType)
@@ -431,15 +441,12 @@ module Lafcadio
431
441
  end
432
442
 
433
443
  class CompoundCondition < Condition #:nodoc:
434
- AND = 1
435
- OR = 2
436
-
437
444
  def initialize( *args )
438
- if( [ AND, OR ].include?( args.last ) )
445
+ if( [ :and, :or ].include?( args.last ) )
439
446
  @compound_type = args.last
440
447
  args.pop
441
448
  else
442
- @compound_type = AND
449
+ @compound_type = :and
443
450
  end
444
451
  @conditions = args.map { |arg|
445
452
  arg.respond_to?( :to_condition ) ? arg.to_condition : arg
@@ -448,7 +455,7 @@ module Lafcadio
448
455
  end
449
456
 
450
457
  def dobj_satisfies?(anObj)
451
- if @compound_type == AND
458
+ if @compound_type == :and
452
459
  @conditions.inject( true ) { |result, cond|
453
460
  result && cond.dobj_satisfies?( anObj )
454
461
  }
@@ -460,25 +467,25 @@ module Lafcadio
460
467
  end
461
468
 
462
469
  def implied_by?( other_condition )
463
- @compound_type == OR && @conditions.any? { |cond|
470
+ @compound_type == :or && @conditions.any? { |cond|
464
471
  cond.implies?( other_condition )
465
472
  }
466
473
  end
467
474
 
468
475
  def implies?( other_condition )
469
476
  super( other_condition ) or (
470
- @compound_type == AND and @conditions.any? { |cond|
477
+ @compound_type == :and and @conditions.any? { |cond|
471
478
  cond.implies? other_condition
472
479
  }
473
480
  ) or (
474
- @compound_type == OR and @conditions.all? { |cond|
481
+ @compound_type == :or and @conditions.all? { |cond|
475
482
  cond.implies? other_condition
476
483
  }
477
484
  )
478
485
  end
479
486
 
480
487
  def to_sql
481
- booleanString = @compound_type == AND ? 'and' : 'or'
488
+ booleanString = @compound_type == :and ? 'and' : 'or'
482
489
  subSqlStrings = @conditions.collect { |cond| cond.to_sql }
483
490
  "(#{ subSqlStrings.join(" #{ booleanString } ") })"
484
491
  end
@@ -500,7 +507,10 @@ module Lafcadio
500
507
  if ( classField = self.domain_class.field( fieldName ) )
501
508
  ObjectFieldImpostor.new( self, classField )
502
509
  else
503
- super( methId, *args )
510
+ msg = "undefined method `" + fieldName +
511
+ "' for #<DomainObjectImpostor::" +
512
+ '#{ domain_class.name }' + ">"
513
+ raise( NoMethodError, msg )
504
514
  end
505
515
  end
506
516
 
@@ -574,16 +584,16 @@ module Lafcadio
574
584
  class Include < CompoundCondition #:nodoc:
575
585
  def initialize( field_name, search_term, domain_class )
576
586
  begin_cond = Like.new(
577
- field_name, search_term + ',', domain_class, Like::POST_ONLY
587
+ field_name, search_term + ',', domain_class, :post_only
578
588
  )
579
589
  mid_cond = Like.new(
580
590
  field_name, ',' + search_term + ',', domain_class
581
591
  )
582
592
  end_cond = Like.new(
583
- field_name, ',' + search_term, domain_class, Like::PRE_ONLY
593
+ field_name, ',' + search_term, domain_class, :pre_only
584
594
  )
585
595
  only_cond = Equals.new( field_name, search_term, domain_class )
586
- super( begin_cond, mid_cond, end_cond, only_cond, OR )
596
+ super( begin_cond, mid_cond, end_cond, only_cond, :or )
587
597
  end
588
598
  end
589
599
 
@@ -593,7 +603,7 @@ module Lafcadio
593
603
  unless args.size == 1
594
604
  h = args.last
595
605
  @order_by = h[:order_by]
596
- @order_by_order = ( h[:order_by_order] or ASC )
606
+ @order_by_order = ( h[:order_by_order] or :asc )
597
607
  @limit = h[:limit]
598
608
  end
599
609
  end
@@ -601,7 +611,7 @@ module Lafcadio
601
611
  def execute
602
612
  impostor = DomainObjectImpostor.impostor @domain_class
603
613
  condition = @action.call( impostor ).to_condition
604
- query = Query.new( @domain_class, condition )
614
+ query = Query.new( @domain_class, :condition => condition )
605
615
  query.order_by = @order_by
606
616
  query.order_by_order = @order_by_order
607
617
  query.limit = @limit
@@ -610,12 +620,8 @@ module Lafcadio
610
620
  end
611
621
 
612
622
  class Like < Condition #:nodoc:
613
- PRE_AND_POST = 1
614
- PRE_ONLY = 2
615
- POST_ONLY = 3
616
-
617
623
  def initialize(
618
- fieldName, searchTerm, domain_class, matchType = PRE_AND_POST
624
+ fieldName, searchTerm, domain_class, matchType = :pre_and_post
619
625
  )
620
626
  if searchTerm.is_a? Regexp
621
627
  searchTerm = process_regexp searchTerm
@@ -637,36 +643,37 @@ module Lafcadio
637
643
 
638
644
  def process_regexp( searchTerm )
639
645
  if searchTerm.source =~ /^\^(.*)/
640
- @matchType = Query::Like::POST_ONLY
646
+ @matchType = :post_only
641
647
  $1
642
648
  elsif searchTerm.source =~ /(.*)\$$/
643
- @matchType = Query::Like::PRE_ONLY
649
+ @matchType = :pre_only
644
650
  $1
645
651
  else
646
- @matchType = Query::Like::PRE_AND_POST
652
+ @matchType = :pre_and_post
647
653
  searchTerm.source
648
654
  end
649
655
  end
650
656
 
651
657
  def regexp
652
- if @matchType == PRE_AND_POST
658
+ if @matchType == :pre_and_post
653
659
  Regexp.new( @searchTerm, Regexp::IGNORECASE )
654
- elsif @matchType == PRE_ONLY
660
+ elsif @matchType == :pre_only
655
661
  Regexp.new( @searchTerm.to_s + "$", Regexp::IGNORECASE )
656
- elsif @matchType == POST_ONLY
662
+ elsif @matchType == :post_only
657
663
  Regexp.new( "^" + @searchTerm, Regexp::IGNORECASE )
658
664
  end
659
665
  end
660
666
 
661
667
  def to_sql
662
- withWildcards = @searchTerm
663
- if @matchType == PRE_AND_POST
668
+ withWildcards = @searchTerm.clone
669
+ if @matchType == :pre_and_post
664
670
  withWildcards = "%" + withWildcards + "%"
665
- elsif @matchType == PRE_ONLY
671
+ elsif @matchType == :pre_only
666
672
  withWildcards = "%" + withWildcards
667
- elsif @matchType == POST_ONLY
673
+ elsif @matchType == :post_only
668
674
  withWildcards += "%"
669
675
  end
676
+ withWildcards.gsub!( /(\\?\.)/ ) { |m| m.size == 1 ? "_" : "." }
670
677
  "#{ db_field_name } like '#{ withWildcards }'"
671
678
  end
672
679
  end
@@ -712,11 +719,7 @@ module Lafcadio
712
719
 
713
720
  class ObjectFieldImpostor #:nodoc:
714
721
  def self.comparators
715
- {
716
- 'lt' => Compare::LESS_THAN, 'lte' => Compare::LESS_THAN_OR_EQUAL,
717
- 'gte' => Compare::GREATER_THAN_OR_EQUAL,
718
- 'gt' => Compare::GREATER_THAN
719
- }
722
+ { 'lt' => :lt, 'lte' => :lte, 'gte' => :gte, 'gt' => :gt }
720
723
  end
721
724
 
722
725
  attr_reader :class_field
@@ -32,7 +32,7 @@
32
32
  # qry = Query.infer(
33
33
  # SKU,
34
34
  # :order_by => [ :standardPrice, :salePrice ],
35
- # :order_by_order => Query::DESC
35
+ # :order_by_order => :desc
36
36
  # ) { |s| s.sku.nil? }
37
37
  # qry.to_sql # => "select * from skus where skus.sku is null order by
38
38
  # standardPrice, salePrice desc"
@@ -108,7 +108,7 @@
108
108
  # }
109
109
  # # => "select * from invoices where (hours = 40 or rate = 50 or client = 99)"
110
110
  # Note that both compound operators can be nested:
111
- # invoices = object_store.getInvoices { |inv|
111
+ # invoices = Invoice.get { |inv|
112
112
  # inv.hours.equals( 40 ) &
113
113
  # ( inv.rate.equals( 50 ) | inv.client.equals( client99 ) )
114
114
  # }
@@ -146,14 +146,13 @@ module Lafcadio
146
146
  # Infers a query from a block. The first required argument is the domain
147
147
  # class. Other optional arguments should be passed in hash form:
148
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.
149
+ # [:order_by_order] Possible values are :asc or :desc. Defaults to :desc.
151
150
  # qry = Query.infer( User ) { |u| u.lname.equals( 'Hwang' ) }
152
151
  # qry.to_sql # => "select * from users where users.lname = 'Hwang'"
153
152
  # qry = Query.infer(
154
153
  # SKU,
155
154
  # :order_by => [ :standardPrice, :salePrice ],
156
- # :order_by_order => Query::DESC
155
+ # :order_by_order => :desc
157
156
  # ) { |s| s.sku.nil? }
158
157
  # qry.to_sql # => "select * from skus where skus.sku is null order by
159
158
  # standardPrice, salePrice desc"
@@ -163,29 +162,25 @@ module Lafcadio
163
162
  end
164
163
 
165
164
  def self.Or( *conditions ) #:nodoc:
166
- conditions << CompoundCondition::OR
165
+ conditions << :or
167
166
  CompoundCondition.new( *conditions)
168
167
  end
169
168
 
170
- ASC = 1
171
- DESC = 2
172
-
173
- attr_reader :domain_class, :condition, :limit
174
- attr_accessor :order_by, :order_by_order
169
+ attr_reader :domain_class, :condition, :include, :limit, :order_by
170
+ attr_accessor :order_by_order
175
171
 
176
- def initialize(domain_class, pk_id_or_condition = nil, opts = {} ) #:nodoc:
172
+ def initialize(domain_class, opts = {} ) #:nodoc:
177
173
  @domain_class, @opts = domain_class, opts
178
174
  ( @condition, @order_by, @limit ) = [ nil, nil, nil ]
179
- if pk_id_or_condition
180
- if pk_id_or_condition.is_a?( Condition )
181
- @condition = pk_id_or_condition
182
- else
183
- @condition = Query::Equals.new(
184
- :pk_id, pk_id_or_condition, domain_class
185
- )
186
- end
175
+ if ( cond = opts[:condition] )
176
+ @condition = cond
177
+ elsif ( pk_id = opts[:pk_id] )
178
+ @condition = Query::Equals.new( :pk_id, pk_id, domain_class )
179
+ end
180
+ if ( @include = opts[:include] )
181
+ @include = [ @include ] unless @include.is_a?( Array )
187
182
  end
188
- @order_by_order = ASC
183
+ @order_by_order = :asc
189
184
  end
190
185
 
191
186
  # Returns a new query representing the condition of the current query and
@@ -195,7 +190,7 @@ module Lafcadio
195
190
  # qry = qry.and { |u| u.fname.equals( 'Francis' ) }
196
191
  # qry.to_sql # => "select * from users where (users.lname = 'Hwang' and
197
192
  # users.fname = 'Francis')"
198
- def and( &action ); compound( CompoundCondition::AND, action ); end
193
+ def and( &action ); compound( :and, action ); end
199
194
 
200
195
  def collect( coll ) #:nodoc:
201
196
  if @opts[:group_functions] == [:count]
@@ -248,10 +243,21 @@ module Lafcadio
248
243
  @limit = limit.is_a?( Fixnum ) ? 0..limit-1 : limit
249
244
  end
250
245
 
251
- def limit_clause #:nodoc:
252
- "limit #{ @limit.begin }, #{ @limit.end - @limit.begin + 1 }" if @limit
246
+ def limit_clause( db ) #:nodoc:
247
+ if @limit
248
+ case db
249
+ when 'Mysql'
250
+ "limit #{ @limit.begin }, #{ @limit.end - @limit.begin + 1 }"
251
+ when 'Pg'
252
+ limit_clause = "limit #{ @limit.end - @limit.begin + 1 }"
253
+ limit_clause += " offset #{ @limit.begin }" if @limit.begin > 0
254
+ limit_clause
255
+ end
256
+ end
253
257
  end
254
258
 
259
+ def one_pk_id?; @condition and @condition.one_pk_id?; end
260
+
255
261
  # Returns a new query representing the condition of the current query and
256
262
  # the new inferred query.
257
263
  # qry = Query.infer( User ) { |u| u.lname.equals( 'Hwang' ) }
@@ -259,7 +265,11 @@ module Lafcadio
259
265
  # qry = qry.or { |u| u.fname.equals( 'Francis' ) }
260
266
  # qry.to_sql # => "select * from users where (users.lname = 'Hwang' or
261
267
  # users.fname = 'Francis')"
262
- def or( &action ); compound( CompoundCondition::OR, action ); end
268
+ def or( &action ); compound( :or, action ); end
269
+
270
+ def order_by=( ob )
271
+ @order_by = ( ob.is_a?( Array ) ? ob.map { |f| f.to_s } : ob.to_s ) if ob
272
+ end
263
273
 
264
274
  def order_clause #:nodoc:
265
275
  if @order_by
@@ -267,7 +277,7 @@ module Lafcadio
267
277
  @domain_class.field( f_name.to_s ).db_field_name
268
278
  }.join( ', ' )
269
279
  clause = "order by #{ field_str } "
270
- clause += @order_by_order == ASC ? 'asc' : 'desc'
280
+ clause += @order_by_order == :asc ? 'asc' : 'desc'
271
281
  clause
272
282
  end
273
283
  end
@@ -282,7 +292,7 @@ module Lafcadio
282
292
  dobj.send order_by
283
293
  end
284
294
  }
285
- objects.reverse! if order_by_order == Query::DESC
295
+ objects.reverse! if order_by_order == :desc
286
296
  objects = objects[limit] if limit
287
297
  objects
288
298
  end
@@ -301,32 +311,38 @@ module Lafcadio
301
311
 
302
312
  def tables #:nodoc:
303
313
  concrete_classes = domain_class.self_and_concrete_superclasses.reverse
304
- table_names = concrete_classes.collect { |domain_class|
305
- domain_class.table_name
306
- }
307
- table_names.join( ', ' )
314
+ sql = ''
315
+ dclass = nil
316
+ until concrete_classes.empty?
317
+ prev_dclass = dclass
318
+ dclass = concrete_classes.shift
319
+ if sql == ''
320
+ sql = dclass.table_name
321
+ else
322
+ sql += " inner join #{ dclass.table_name } on #{ sql_primary_key_field( prev_dclass ) } = #{ sql_primary_key_field( dclass ) }"
323
+ end
324
+ end
325
+ if @include
326
+ @include.each do |include_sym|
327
+ field = dclass.field include_sym
328
+ included_dclass = field.linked_type
329
+ sql += " left outer join #{ included_dclass.table_name } on #{ dclass.table_name }.#{ field.db_field_name } = #{ sql_primary_key_field( included_dclass ) }"
330
+ end
331
+ end
332
+ sql
308
333
  end
309
334
 
310
- def to_sql
335
+ def to_sql( db = 'Mysql' )
311
336
  clauses = [ "select #{ fields }", "from #{ tables }" ]
312
337
  clauses << where_clause if where_clause
313
338
  clauses << order_clause if order_clause
314
- clauses << limit_clause if limit_clause
339
+ clauses << limit_clause( db ) if limit_clause( db )
315
340
  clauses.join ' '
316
341
  end
317
342
 
318
343
  def where_clause #:nodoc:
319
- concrete_classes = domain_class.self_and_concrete_superclasses.reverse
320
344
  where_clauses = []
321
- concrete_classes.each_with_index { |domain_class, i|
322
- if i < concrete_classes.size - 1
323
- join_clause = sql_primary_key_field( domain_class ) + ' = ' +
324
- sql_primary_key_field( concrete_classes[i+1] )
325
- where_clauses << join_clause
326
- else
327
- where_clauses << @condition.to_sql if @condition
328
- end
329
- }
345
+ where_clauses << @condition.to_sql if @condition
330
346
  !where_clauses.empty? ? 'where ' + where_clauses.join( ' and ' ) : nil
331
347
  end
332
348
 
@@ -376,32 +392,24 @@ module Lafcadio
376
392
  end
377
393
 
378
394
  def not; Query::Not.new( self ); end
395
+
396
+ def one_pk_id?; self.is_a?( Equals ) and primary_key_field?; end
379
397
 
380
398
  def primary_key_field?; 'pk_id' == @fieldName; end
381
399
 
382
- def query; Query.new( @domain_class, self ); end
400
+ def query; Query.new( @domain_class, :condition => self ); end
383
401
 
384
402
  def to_condition; self; end
385
403
  end
386
404
 
387
405
  class Compare < Condition #:nodoc:
388
- LESS_THAN = 1
389
- LESS_THAN_OR_EQUAL = 2
390
- GREATER_THAN_OR_EQUAL = 3
391
- GREATER_THAN = 4
392
-
393
- @@comparators = {
394
- LESS_THAN => '<',
395
- LESS_THAN_OR_EQUAL => '<=',
396
- GREATER_THAN_OR_EQUAL => '>=',
397
- GREATER_THAN => '>'
398
- }
406
+ @@comparators = { :lt => '<', :lte => '<=', :gte => '>=', :gt => '>' }
399
407
 
400
408
  @@mockComparators = {
401
- LESS_THAN => Proc.new { |d1, d2| d1 < d2 },
402
- LESS_THAN_OR_EQUAL => Proc.new { |d1, d2| d1 <= d2 },
403
- GREATER_THAN_OR_EQUAL => Proc.new { |d1, d2| d1 >= d2 },
404
- GREATER_THAN => Proc.new { |d1, d2| d1 > d2 }
409
+ :lt => Proc.new { |d1, d2| d1 < d2 },
410
+ :lte => Proc.new { |d1, d2| d1 <= d2 },
411
+ :gte => Proc.new { |d1, d2| d1 >= d2 },
412
+ :gt => Proc.new { |d1, d2| d1 > d2 }
405
413
  }
406
414
 
407
415
  def initialize(fieldName, searchTerm, domain_class, compareType)
@@ -431,15 +439,12 @@ module Lafcadio
431
439
  end
432
440
 
433
441
  class CompoundCondition < Condition #:nodoc:
434
- AND = 1
435
- OR = 2
436
-
437
442
  def initialize( *args )
438
- if( [ AND, OR ].include?( args.last ) )
443
+ if( [ :and, :or ].include?( args.last ) )
439
444
  @compound_type = args.last
440
445
  args.pop
441
446
  else
442
- @compound_type = AND
447
+ @compound_type = :and
443
448
  end
444
449
  @conditions = args.map { |arg|
445
450
  arg.respond_to?( :to_condition ) ? arg.to_condition : arg
@@ -448,7 +453,7 @@ module Lafcadio
448
453
  end
449
454
 
450
455
  def dobj_satisfies?(anObj)
451
- if @compound_type == AND
456
+ if @compound_type == :and
452
457
  @conditions.inject( true ) { |result, cond|
453
458
  result && cond.dobj_satisfies?( anObj )
454
459
  }
@@ -460,25 +465,25 @@ module Lafcadio
460
465
  end
461
466
 
462
467
  def implied_by?( other_condition )
463
- @compound_type == OR && @conditions.any? { |cond|
468
+ @compound_type == :or && @conditions.any? { |cond|
464
469
  cond.implies?( other_condition )
465
470
  }
466
471
  end
467
472
 
468
473
  def implies?( other_condition )
469
474
  super( other_condition ) or (
470
- @compound_type == AND and @conditions.any? { |cond|
475
+ @compound_type == :and and @conditions.any? { |cond|
471
476
  cond.implies? other_condition
472
477
  }
473
478
  ) or (
474
- @compound_type == OR and @conditions.all? { |cond|
479
+ @compound_type == :or and @conditions.all? { |cond|
475
480
  cond.implies? other_condition
476
481
  }
477
482
  )
478
483
  end
479
484
 
480
485
  def to_sql
481
- booleanString = @compound_type == AND ? 'and' : 'or'
486
+ booleanString = @compound_type == :and ? 'and' : 'or'
482
487
  subSqlStrings = @conditions.collect { |cond| cond.to_sql }
483
488
  "(#{ subSqlStrings.join(" #{ booleanString } ") })"
484
489
  end
@@ -500,7 +505,10 @@ module Lafcadio
500
505
  if ( classField = self.domain_class.field( fieldName ) )
501
506
  ObjectFieldImpostor.new( self, classField )
502
507
  else
503
- super( methId, *args )
508
+ msg = "undefined method `" + fieldName +
509
+ "' for #<DomainObjectImpostor::" +
510
+ '#{ domain_class.name }' + ">"
511
+ raise( NoMethodError, msg )
504
512
  end
505
513
  end
506
514
 
@@ -574,16 +582,16 @@ module Lafcadio
574
582
  class Include < CompoundCondition #:nodoc:
575
583
  def initialize( field_name, search_term, domain_class )
576
584
  begin_cond = Like.new(
577
- field_name, search_term + ',', domain_class, Like::POST_ONLY
585
+ field_name, search_term + ',', domain_class, :post_only
578
586
  )
579
587
  mid_cond = Like.new(
580
588
  field_name, ',' + search_term + ',', domain_class
581
589
  )
582
590
  end_cond = Like.new(
583
- field_name, ',' + search_term, domain_class, Like::PRE_ONLY
591
+ field_name, ',' + search_term, domain_class, :pre_only
584
592
  )
585
593
  only_cond = Equals.new( field_name, search_term, domain_class )
586
- super( begin_cond, mid_cond, end_cond, only_cond, OR )
594
+ super( begin_cond, mid_cond, end_cond, only_cond, :or )
587
595
  end
588
596
  end
589
597
 
@@ -593,7 +601,7 @@ module Lafcadio
593
601
  unless args.size == 1
594
602
  h = args.last
595
603
  @order_by = h[:order_by]
596
- @order_by_order = ( h[:order_by_order] or ASC )
604
+ @order_by_order = ( h[:order_by_order] or :asc )
597
605
  @limit = h[:limit]
598
606
  end
599
607
  end
@@ -601,7 +609,7 @@ module Lafcadio
601
609
  def execute
602
610
  impostor = DomainObjectImpostor.impostor @domain_class
603
611
  condition = @action.call( impostor ).to_condition
604
- query = Query.new( @domain_class, condition )
612
+ query = Query.new( @domain_class, :condition => condition )
605
613
  query.order_by = @order_by
606
614
  query.order_by_order = @order_by_order
607
615
  query.limit = @limit
@@ -610,12 +618,8 @@ module Lafcadio
610
618
  end
611
619
 
612
620
  class Like < Condition #:nodoc:
613
- PRE_AND_POST = 1
614
- PRE_ONLY = 2
615
- POST_ONLY = 3
616
-
617
621
  def initialize(
618
- fieldName, searchTerm, domain_class, matchType = PRE_AND_POST
622
+ fieldName, searchTerm, domain_class, matchType = :pre_and_post
619
623
  )
620
624
  if searchTerm.is_a? Regexp
621
625
  searchTerm = process_regexp searchTerm
@@ -637,36 +641,37 @@ module Lafcadio
637
641
 
638
642
  def process_regexp( searchTerm )
639
643
  if searchTerm.source =~ /^\^(.*)/
640
- @matchType = Query::Like::POST_ONLY
644
+ @matchType = :post_only
641
645
  $1
642
646
  elsif searchTerm.source =~ /(.*)\$$/
643
- @matchType = Query::Like::PRE_ONLY
647
+ @matchType = :pre_only
644
648
  $1
645
649
  else
646
- @matchType = Query::Like::PRE_AND_POST
650
+ @matchType = :pre_and_post
647
651
  searchTerm.source
648
652
  end
649
653
  end
650
654
 
651
655
  def regexp
652
- if @matchType == PRE_AND_POST
656
+ if @matchType == :pre_and_post
653
657
  Regexp.new( @searchTerm, Regexp::IGNORECASE )
654
- elsif @matchType == PRE_ONLY
658
+ elsif @matchType == :pre_only
655
659
  Regexp.new( @searchTerm.to_s + "$", Regexp::IGNORECASE )
656
- elsif @matchType == POST_ONLY
660
+ elsif @matchType == :post_only
657
661
  Regexp.new( "^" + @searchTerm, Regexp::IGNORECASE )
658
662
  end
659
663
  end
660
664
 
661
665
  def to_sql
662
- withWildcards = @searchTerm
663
- if @matchType == PRE_AND_POST
666
+ withWildcards = @searchTerm.clone
667
+ if @matchType == :pre_and_post
664
668
  withWildcards = "%" + withWildcards + "%"
665
- elsif @matchType == PRE_ONLY
669
+ elsif @matchType == :pre_only
666
670
  withWildcards = "%" + withWildcards
667
- elsif @matchType == POST_ONLY
671
+ elsif @matchType == :post_only
668
672
  withWildcards += "%"
669
673
  end
674
+ withWildcards.gsub!( /(\\?\.)/ ) { |m| m.size == 1 ? "_" : "." }
670
675
  "#{ db_field_name } like '#{ withWildcards }'"
671
676
  end
672
677
  end
@@ -712,11 +717,7 @@ module Lafcadio
712
717
 
713
718
  class ObjectFieldImpostor #:nodoc:
714
719
  def self.comparators
715
- {
716
- 'lt' => Compare::LESS_THAN, 'lte' => Compare::LESS_THAN_OR_EQUAL,
717
- 'gte' => Compare::GREATER_THAN_OR_EQUAL,
718
- 'gt' => Compare::GREATER_THAN
719
- }
720
+ { 'lt' => :lt, 'lte' => :lte, 'gte' => :gte, 'gt' => :gt }
720
721
  end
721
722
 
722
723
  attr_reader :class_field