From bd473344000bd538d353e4bc6d20ca8fff2e4704 Mon Sep 17 00:00:00 2001 From: Ernie Miller Date: Fri, 9 Apr 2010 19:52:43 -0400 Subject: Support predicate complements and alternate not syntax (overload BasicObject#!) --- lib/arel/algebra/attributes/attribute.rb | 4 +- lib/arel/algebra/predicates.rb | 144 ++++++++++++++++++---- lib/arel/engines/memory/predicates.rb | 39 +++--- lib/arel/engines/sql/core_extensions/nil_class.rb | 4 + lib/arel/engines/sql/core_extensions/object.rb | 4 + lib/arel/engines/sql/predicates.rb | 14 +-- lib/arel/engines/sql/primitives.rb | 8 ++ 7 files changed, 166 insertions(+), 51 deletions(-) (limited to 'lib') diff --git a/lib/arel/algebra/attributes/attribute.rb b/lib/arel/algebra/attributes/attribute.rb index 0266b38db3..640d6facde 100644 --- a/lib/arel/algebra/attributes/attribute.rb +++ b/lib/arel/algebra/attributes/attribute.rb @@ -104,12 +104,12 @@ module Arel ", :any => " def #{name}_any(*others) - Predicates::Any.new(Predicates::#{klass}, self, *others) + Predicates::Any.build(Predicates::#{klass}, self, *others) end ", :all => " def #{name}_all(*others) - Predicates::All.new(Predicates::#{klass}, self, *others) + Predicates::All.build(Predicates::#{klass}, self, *others) end " } diff --git a/lib/arel/algebra/predicates.rb b/lib/arel/algebra/predicates.rb index be80f2a987..2da37af2e7 100644 --- a/lib/arel/algebra/predicates.rb +++ b/lib/arel/algebra/predicates.rb @@ -9,32 +9,52 @@ module Arel And.new(self, other_predicate) end - def not + def complement Not.new(self) end + + def not + complement + end + + if respond_to?('!') # Nice! We're running Ruby 1.9 and can override the inherited BasicObject#! + def empty? # Need to define empty? to keep Object#blank? from going haywire + false + end + + define_method('!') do + self.complement + end + end end class Polyadic < Predicate - attributes :operator, :operand1, :additional_operands + attributes :predicates - def initialize(operator, operand1, *additional_operands) - @operator = operator - @operand1 = operand1 - @additional_operands = additional_operands.uniq + def initialize(*predicates) + @predicates = predicates + end + + # Build a Polyadic predicate based on: + # * operator - The Predicate subclass that defines the type of operation + # (LessThan, Equality, etc) + # * operand1 - The left-hand operand (normally an Arel::Attribute) + # * additional_operands - All possible right-hand operands + def self.build(operator, operand1, *additional_operands) + new( + *additional_operands.uniq.inject([]) do |predicates, operand| + predicates << operator.new(operand1, operand) + end + ) end def ==(other) - self.class === other and - @operator == operator and - @operand1 == other.operand1 and - same_elements?(@additional_operands, other.additional_operands) + same_elements?(@predicates, other.predicates) end def bind(relation) self.class.new( - operator, - operand1.find_correlate_in(relation), - *additional_operands.map {|o| o.find_correlate_in(relation)} + *predicates.map {|p| p.find_correlate_in(relation)} ) end @@ -49,6 +69,18 @@ module Arel end end + class Any < Polyadic + def complement + All.new(*predicates.map {|p| p.complement}) + end + end + + class All < Polyadic + def complement + Any.new(*predicates.map {|p| p.complement}) + end + end + class Unary < Predicate attributes :operand deriving :initialize, :== @@ -57,6 +89,12 @@ module Arel self.class.new(operand.find_correlate_in(relation)) end end + + class Not < Unary + def complement + operand + end + end class Binary < Predicate attributes :operand1, :operand2 @@ -72,6 +110,20 @@ module Arel self.class.new(operand1.find_correlate_in(relation), operand2.find_correlate_in(relation)) end end + + class CompoundPredicate < Binary; end + + class And < CompoundPredicate + def complement + Or.new(operand1.complement, operand2.complement) + end + end + + class Or < CompoundPredicate + def complement + And.new(operand1.complement, operand2.complement) + end + end class Equality < Binary def ==(other) @@ -79,16 +131,64 @@ module Arel ((operand1 == other.operand1 and operand2 == other.operand2) or (operand1 == other.operand2 and operand2 == other.operand1)) end + + def complement + Inequality.new(operand1, operand2) + end end - class Inequality < Equality; end - class GreaterThanOrEqualTo < Binary; end - class GreaterThan < Binary; end - class LessThanOrEqualTo < Binary; end - class LessThan < Binary; end - class Match < Binary; end - class NotMatch < Binary; end - class In < Binary; end - class NotIn < Binary; end + class Inequality < Equality + def complement + Equality.new(operand1, operand2) + end + end + + class GreaterThanOrEqualTo < Binary + def complement + LessThan.new(operand1, operand2) + end + end + + class GreaterThan < Binary + def complement + LessThanOrEqualTo.new(operand1, operand2) + end + end + + class LessThanOrEqualTo < Binary + def complement + GreaterThan.new(operand1, operand2) + end + end + + class LessThan < Binary + def complement + GreaterThanOrEqualTo.new(operand1, operand2) + end + end + + class Match < Binary + def complement + NotMatch.new(operand1, operand2) + end + end + + class NotMatch < Binary + def complement + Match.new(operand1, operand2) + end + end + + class In < Binary + def complement + NotIn.new(operand1, operand2) + end + end + + class NotIn < Binary + def complement + In.new(operand1, operand2) + end + end end end diff --git a/lib/arel/engines/memory/predicates.rb b/lib/arel/engines/memory/predicates.rb index c0ee862626..0e88810e7d 100644 --- a/lib/arel/engines/memory/predicates.rb +++ b/lib/arel/engines/memory/predicates.rb @@ -13,41 +13,40 @@ module Arel end class Not < Unary - def operator; '!' end - end - - class CompoundPredicate < Binary def eval(row) - eval "operand1.eval(row) #{operator} operand2.eval(row)" + !operand.eval(row) end end - - class Or < CompoundPredicate - def operator; :or end - end - - class And < CompoundPredicate - def operator; :and end - end - class GroupedPredicate < Polyadic + class Polyadic < Predicate def eval(row) - group = additional_operands.inject([]) do |results, operand| - results << operator.new(operand1, operand) - end - group.send(compounder) do |operation| + predicates.send(compounder) do |operation| operation.eval(row) end end end - class Any < GroupedPredicate + class Any < Polyadic def compounder; :any? end end - class All < GroupedPredicate + class All < Polyadic def compounder; :all? end end + + class CompoundPredicate < Binary + def eval(row) + eval "operand1.eval(row) #{operator} operand2.eval(row)" + end + end + + class Or < CompoundPredicate + def operator; :or end + end + + class And < CompoundPredicate + def operator; :and end + end class Equality < Binary def operator; :== end diff --git a/lib/arel/engines/sql/core_extensions/nil_class.rb b/lib/arel/engines/sql/core_extensions/nil_class.rb index 3f70677ba6..d4bb0e4c33 100644 --- a/lib/arel/engines/sql/core_extensions/nil_class.rb +++ b/lib/arel/engines/sql/core_extensions/nil_class.rb @@ -4,6 +4,10 @@ module Arel def equality_predicate_sql 'IS' end + + def inequality_predicate_sql + 'IS NOT' + end def not_predicate_sql 'IS NOT' diff --git a/lib/arel/engines/sql/core_extensions/object.rb b/lib/arel/engines/sql/core_extensions/object.rb index b71ef29fd5..5415c84706 100644 --- a/lib/arel/engines/sql/core_extensions/object.rb +++ b/lib/arel/engines/sql/core_extensions/object.rb @@ -8,6 +8,10 @@ module Arel def equality_predicate_sql '=' end + + def inequality_predicate_sql + '!=' + end def not_predicate_sql '!=' diff --git a/lib/arel/engines/sql/predicates.rb b/lib/arel/engines/sql/predicates.rb index b459168620..df8700a500 100644 --- a/lib/arel/engines/sql/predicates.rb +++ b/lib/arel/engines/sql/predicates.rb @@ -30,21 +30,19 @@ module Arel def predicate_sql; "AND" end end - class GroupedPredicate < Polyadic + class Polyadic < Predicate def to_sql(formatter = nil) "(" + - additional_operands.inject([]) { |predicates, operand| - predicates << operator.new(operand1, operand).to_sql(formatter) - }.join(" #{predicate_sql} ") + + predicates.map {|p| p.to_sql(formatter)}.join(" #{predicate_sql} ") + ")" end end - class Any < GroupedPredicate + class Any < Polyadic def predicate_sql; "OR" end end - class All < GroupedPredicate + class All < Polyadic def predicate_sql; "AND" end end @@ -55,7 +53,9 @@ module Arel end class Inequality < Equality - def predicate_sql; '!=' end + def predicate_sql + operand2.inequality_predicate_sql + end end class GreaterThanOrEqualTo < Binary diff --git a/lib/arel/engines/sql/primitives.rb b/lib/arel/engines/sql/primitives.rb index 78e1ed7f0b..41769fa510 100644 --- a/lib/arel/engines/sql/primitives.rb +++ b/lib/arel/engines/sql/primitives.rb @@ -29,10 +29,18 @@ module Arel def inclusion_predicate_sql value.inclusion_predicate_sql end + + def exclusion_predicate_sql + value.exclusion_predicate_sql + end def equality_predicate_sql value.equality_predicate_sql end + + def inequality_predicate_sql + value.inequality_predicate_sql + end def not_predicate_sql value.not_predicate_sql -- cgit v1.2.3