Class: Caboose::EZ::Condition

Inherits:
Object
  • Object
show all
Defined in:
lib/og/ez/condition.rb

Overview

EZ::Condition plugin for generating the :conditions where clause for ActiveRecord::Base.find. And an extension to ActiveRecord::Base called AR::Base.find_with_conditions that takes a block and builds the where clause dynamically for you.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(*args, &block) ⇒ Condition

Initialize @clauses and eval the block so it invokes method_missing.


25
26
27
28
29
30
31
32
33
# File 'lib/og/ez/condition.rb', line 25

def initialize(*args, &block)
  options = args.last.is_a?(Hash) ? args.last : {}
  options[:table_name] = args.first if args.first.kind_of? Symbol        
  @table_name = options.delete(:table_name) || nil
  @outer = options.delete(:outer) || :and
  @inner = options.delete(:inner) || :and
  @clauses = []   
  instance_eval(&block) if block_given?
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(name, *args, &block) ⇒ Object

When invoked with the name of the column in each statement inside the block: A new Clause instance is created and recieves the args. Then the operator hits method_missing and gets sent to a new Clause instance where it either matches one of the defined ops or hits method_missing there.

When invoked with an attached block a subcondition is created. The name is regarded as the table_name, additional parameters for outer and inner are passed on.


43
44
45
46
47
48
49
50
51
52
# File 'lib/og/ez/condition.rb', line 43

def method_missing(name, *args, &block)
  if block_given?         
    # handle name as table_name and create a subcondition
    options = args.last.is_a?(Hash) ? args.last : {}
    options[:table_name] ||= name
    define_sub(options, &block)
  else
    clause(name, *args)
  end
end

Instance Attribute Details

#clausesObject (readonly)

these are also reserved words regarding SQL column names use esc_* prefix to circumvent any issues


19
20
21
# File 'lib/og/ez/condition.rb', line 19

def clauses
  @clauses
end

#innerObject

Returns the value of attribute inner


20
21
22
# File 'lib/og/ez/condition.rb', line 20

def inner
  @inner
end

#outerObject

Returns the value of attribute outer


21
22
23
# File 'lib/og/ez/condition.rb', line 21

def outer
  @outer
end

Instance Method Details

#<<(condition, outer = nil) ⇒ Object Also known as: sql_condition, add_sql, clone_from, append

Append a condition element, which can be one of the following:

  • String: raw sql string

  • ActiveRecord instance, for attribute or PK cloning

  • Condition or Clause with to_sql method and outer property

  • Array in ActiveRecord format ['column = ?', 2]


113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/og/ez/condition.rb', line 113

def <<(condition, outer = nil)
  if condition.kind_of?(String) and not condition.to_s.empty? 
    cond = SqlClause.new(condition)
    cond.outer = outer || :and      
    @clauses << cond
  elsif condition.kind_of?(Og::EntityMixin)
    if condition.attributes[condition.class.primary_key].nil?
      condition.attributes.each { |k, v| clause([condition.class.table_name, k]) == v unless v.to_s.empty? } 
    else
      clause([condition.class.table_name, condition.class.primary_key]) == condition.attributes[condition.class.primary_key]
    end                            
  else          
    if condition.kind_of?(Condition) or condition.kind_of?(AbstractClause)
      logic = condition.outer if outer.nil?
      condition = condition.to_sql 
    else
      logic = outer
    end
    if condition.kind_of?(Array) and not condition.empty?
      array_clause = ArrayClause.new(condition)
      array_clause.outer = logic
      @clauses << array_clause
    end
  end       
end

#and_condition(*args, &block) ⇒ Object Also known as: all

Shortcut for adding a :and boolean joined subcondition


85
86
87
88
89
90
91
# File 'lib/og/ez/condition.rb', line 85

def and_condition(*args, &block)
  options = args.last.is_a?(Hash) ? args.last : {}
  options[:table_name] = args.first if args.first.kind_of? Symbol
  options[:outer] ||= @outer
  options[:inner] ||= :and
  define_sub(options, &block)
end

#clause(name, *args) ⇒ Object

You can define clauses dynamicly using this method. It will take a clause and create the correct Clause object to process the conditions


56
57
58
59
60
61
62
63
64
65
66
# File 'lib/og/ez/condition.rb', line 56

def clause(name, *args)
  if name.kind_of?(Array)
    c = Clause.new(name.first, name.last)
  elsif args.last.kind_of?(Symbol)
    c = Clause.new(args.pop, name)
  else 
    c = Clause.new(@table_name, name)
  end
  @clauses << c
  c
end

#define_sub(*args, &block) ⇒ Object Also known as: sub, condition

Create subcondition from a block, optionally specifying table_name, outer and inner. :outer determines how the subcondition is added to the condition, while :inner determines the internal 'joining' of conditions inside the subcondition. Both :inner & :outer defult to 'AND'


72
73
74
75
76
77
78
# File 'lib/og/ez/condition.rb', line 72

def define_sub(*args, &block)
  options = args.last.is_a?(Hash) ? args.last : {}
  options[:table_name] = args.first if args.first.kind_of? Symbol
  options[:table_name] ||= @table_name
  cond = Condition.new(options, &block)
  self << cond
end

#or_condition(*args, &block) ⇒ Object Also known as: any

Shortcut for adding a :or boolean joined subcondition


97
98
99
100
101
102
103
# File 'lib/og/ez/condition.rb', line 97

def or_condition(*args, &block)
  options = args.last.is_a?(Hash) ? args.last : {}
  options[:table_name] = args.first if args.first.kind_of? Symbol
  options[:outer] ||= @outer
  options[:inner] ||= :or
  define_sub(options, &block)
end

#to_sql(logic = @inner) ⇒ Object

Loop over all Clause onjects in @clauses array and call to_sql on each instance. Then join the queries and params into the :conditions array with logic defaulting to AND. Subqueries are joined together using their individual outer property setting if present. Also defaults to AND.


154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
# File 'lib/og/ez/condition.rb', line 154

def to_sql(logic=@inner)
  params = []; query = []
  @clauses.each do |cv|
    q, p, e = cv.to_sql                 
    unless q.to_s.empty?
      logic = cv.outer ? cv.outer : logic
      logic = logic.to_s.upcase
      logic = 'AND NOT' if logic == 'NOT'
      query << logic unless query.empty?
      query << q
      if cv.test == :in 
        params << p if p.respond_to?(:map)
      elsif p.kind_of?(Array)
        p.flatten! unless q =~ /IN/
        params += p
      else
        params << p unless p.nil?
        params << e unless e.nil?
      end  
    end       
  end       
  [query.join(' '), *params ]
end