Class: Squeel::Visitors::AttributeVisitor

Inherits:
Base
  • Object
show all
Defined in:
lib/squeel/visitors/attribute_visitor.rb

Overview

A visitor that tries to convert visited nodes into Arel::Attributes or other nodes that can be used for grouping, ordering, and the like.

Constant Summary

Constants inherited from Base

Base::DISPATCH

Instance Attribute Summary

Attributes inherited from Base

#context

Instance Method Summary collapse

Methods inherited from Base

#accept, #can_accept?, can_accept?, #initialize, #quote, #quoted?, #visit

Constructor Details

This class inherits a constructor from Squeel::Visitors::Base

Instance Method Details

#implies_context_change?(v) ⇒ Boolean (private)

Returns Whether the given value implies a context change.

Parameters:

  • v

    The value to consider

Returns:

  • (Boolean)

    Whether the given value implies a context change



150
151
152
# File 'lib/squeel/visitors/attribute_visitor.rb', line 150

def implies_context_change?(v)
  can_accept?(v)
end

#visit_Array(o, parent) ⇒ Array (private)

Visit elements of an array that it’s possible to visit – leave other elements untouched.

Parameters:

  • o (Array)

    The array to visit

  • parent

    The array’s parent within the context

Returns:

  • (Array)

    The flattened array with elements visited



33
34
35
# File 'lib/squeel/visitors/attribute_visitor.rb', line 33

def visit_Array(o, parent)
  o.map { |v| can_accept?(v) ? accept(v, parent) : v }.flatten
end

#visit_Hash(o, parent) ⇒ Array (private)

Visit a Hash. This entails iterating through each key and value and visiting each value in turn.

Parameters:

  • o (Hash)

    The Hash to visit

  • parent

    The current parent object in the context

Returns:

  • (Array)

    An array of values for use in an ordering, grouping, etc.



17
18
19
20
21
22
23
24
25
# File 'lib/squeel/visitors/attribute_visitor.rb', line 17

def visit_Hash(o, parent)
  o.map do |k, v|
    if implies_context_change?(v)
      visit_with_context_change(k, v, parent)
    else
      visit_without_context_change(k, v, parent)
    end
  end.flatten
end

#visit_Squeel_Nodes_As(o, parent) ⇒ Arel::Nodes::As (private)

Visit a Squeel As node, resulting in am ARel As node.

Parameters:

  • The (Nodes::As)

    As node to visit

  • parent

    The parent object in the context

Returns:

  • (Arel::Nodes::As)

    The resulting as node.



144
145
146
# File 'lib/squeel/visitors/attribute_visitor.rb', line 144

def visit_Squeel_Nodes_As(o, parent)
  accept(o.left, parent).as(o.right)
end

#visit_Squeel_Nodes_Function(o, parent) ⇒ Object (private)

Visit a Function node. Each function argument will be accepted or contextualized if appropriate. Keep in mind that this occurs with the current parent within the context.

Examples:

A function as the endpoint of a keypath

Person.joins{children}.order{children.coalesce(name, '<no name>')}
# => SELECT "people".* FROM "people"
       INNER JOIN "people" "children_people"
         ON "children_people"."parent_id" = "people"."id"
       ORDER BY coalesce("children_people"."name", '<no name>')

Parameters:

  • o (Nodes::Function)

    The function node to visit

  • parent

    The node’s parent within the context



92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/squeel/visitors/attribute_visitor.rb', line 92

def visit_Squeel_Nodes_Function(o, parent)
  args = o.args.map do |arg|
    case arg
    when Nodes::Function, Nodes::KeyPath
      accept(arg, parent)
    when Symbol, Nodes::Stub
      Arel.sql(arel_visitor.accept contextualize(parent)[arg.to_sym])
    else
      quote arg
    end
  end
  Arel::Nodes::NamedFunction.new(o.name, args, o.alias)
end

#visit_Squeel_Nodes_KeyPath(o, parent) ⇒ Object (private)

Visit a keypath. This will traverse the keypath’s “path”, setting a new parent as though the keypath’s endpoint was in a deeply-nested hash, then visit the endpoint with the new parent.

Parameters:

  • o (Nodes::KeyPath)

    The keypath to visit

  • parent

    The keypath’s parent within the context

Returns:

  • The visited endpoint, with the parent from the KeyPath’s path.



64
65
66
67
68
# File 'lib/squeel/visitors/attribute_visitor.rb', line 64

def visit_Squeel_Nodes_KeyPath(o, parent)
  parent = traverse(o, parent)

  accept(o.endpoint, parent)
end

#visit_Squeel_Nodes_Operation(o, parent) ⇒ Object (private)

Visit an Operation node. Each operand will be accepted or contextualized if appropriate. Keep in mind that this occurs with the current parent within the context.

Parameters:

  • o (Nodes::Operation)

    The operation node to visit

  • parent

    The node’s parent within the context



112
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/squeel/visitors/attribute_visitor.rb', line 112

def visit_Squeel_Nodes_Operation(o, parent)
  args = o.args.map do |arg|
    case arg
    when Nodes::Function
      accept(arg, parent)
    when Symbol, Nodes::Stub
      Arel.sql(arel_visitor.accept contextualize(parent)[arg.to_sym])
    else
      quote arg
    end
  end

  op = case o.operator
  when :+
    Arel::Nodes::Addition.new(args[0], args[1])
  when :-
    Arel::Nodes::Subtraction.new(args[0], args[1])
  when :*
    Arel::Nodes::Multiplication.new(args[0], args[1])
  when :/
    Arel::Nodes::Division.new(args[0], args[1])
  else
    Arel.sql("#{arel_visitor.accept(args[0])} #{o.operator} #{arel_visitor.accept(args[1])}")
  end
  o.alias ? op.as(o.alias) : op
end

#visit_Squeel_Nodes_Order(o, parent) ⇒ Arel::Nodes::Ordering (private)

Visit an Order node.

Parameters:

  • o (Nodes::Order)

    The order node to visit

  • parent

    The node’s parent within the context

Returns:

  • (Arel::Nodes::Ordering)

    An ascending or desending ordering



75
76
77
# File 'lib/squeel/visitors/attribute_visitor.rb', line 75

def visit_Squeel_Nodes_Order(o, parent)
  accept(o.expr, parent).send(o.descending? ? :desc : :asc)
end

#visit_Squeel_Nodes_Stub(o, parent) ⇒ Arel::Attribute (private)

Visit a stub. This will return an attribute named after the stub against the current parent’s contextualized table.

Parameters:

  • o (Nodes::Stub)

    The stub to visit

  • parent

    The stub’s parent within the context

Returns:

  • (Arel::Attribute)

    An attribute on the contextualized parent table



53
54
55
# File 'lib/squeel/visitors/attribute_visitor.rb', line 53

def visit_Squeel_Nodes_Stub(o, parent)
  contextualize(parent)[o.symbol]
end

#visit_Symbol(o, parent) ⇒ Arel::Attribute (private)

Visit a symbol. This will return an attribute named after the symbol against the current parent’s contextualized table.

Parameters:

  • o (Symbol)

    The symbol to visit

  • parent

    The symbol’s parent within the context

Returns:

  • (Arel::Attribute)

    An attribute on the contextualized parent table



43
44
45
# File 'lib/squeel/visitors/attribute_visitor.rb', line 43

def visit_Symbol(o, parent)
  contextualize(parent)[o]
end

#visit_with_context_change(k, v, parent) ⇒ Object (private)

Change context (by setting the new parent to the result of a #find or #traverse on the key), then accept the given value.

Parameters:

  • k

    The hash key

  • v

    The hash value

  • parent

    The current parent object in the context

Returns:

  • The visited value



161
162
163
164
165
166
167
168
169
170
171
172
173
174
# File 'lib/squeel/visitors/attribute_visitor.rb', line 161

def visit_with_context_change(k, v, parent)
  parent = case k
    when Nodes::KeyPath
      traverse(k, parent, true)
    else
      find(k, parent)
    end

  if Array === v
    v.map {|val| accept(val, parent || k)}
  else
    can_accept?(v) ? accept(v, parent || k) : v
  end
end

#visit_without_context_change(k, v, parent) ⇒ Object (private)

If there is no context change, we’ll just return the value unchanged, currently. Is this really the right behavior? I don’t think so, but it works in this case.

Parameters:

  • k

    The hash key

  • v

    The hash value

  • parent

    The current parent object in the context

Returns:

  • The same value we just received. Yeah, this method’s pretty pointless, for now, and only here for consistency’s sake.



185
186
187
# File 'lib/squeel/visitors/attribute_visitor.rb', line 185

def visit_without_context_change(k, v, parent)
  v
end