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_visit?, can_visit?, #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



170
171
172
# File 'lib/squeel/visitors/attribute_visitor.rb', line 170

def implies_context_change?(v)
  can_visit?(v)
end

#visit_ActiveRecord_Relation(o, parent) ⇒ Arel::SelectManager (private)

Visit an ActiveRecord Relation, returning an Arel::SelectManager

Parameters:

  • o (ActiveRecord::Relation)

    The Relation to visit

  • parent

    The parent object in the context

Returns:

  • (Arel::SelectManager)

    The ARel select manager that represents the relation’s query



164
165
166
# File 'lib/squeel/visitors/attribute_visitor.rb', line 164

def visit_ActiveRecord_Relation(o, parent)
  o.arel
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_visit?(v) ? visit(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.



155
156
157
# File 'lib/squeel/visitors/attribute_visitor.rb', line 155

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

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

Visit a Function node. Each function argument will be visiteded 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



101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# File 'lib/squeel/visitors/attribute_visitor.rb', line 101

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

  o.alias ? func.as(o.alias) : func
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.



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

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

  visit(o.endpoint, parent)
end

#visit_Squeel_Nodes_Literal(o, parent) ⇒ Arel::Nodes::SqlLiteral (private)

Visit a Literal by converting it to an ARel SqlLiteral

Parameters:

  • o (Nodes::Literal)

    The Literal to visit

  • parent

    The parent object in the context (unused)

Returns:

  • (Arel::Nodes::SqlLiteral)

    An SqlLiteral



62
63
64
# File 'lib/squeel/visitors/attribute_visitor.rb', line 62

def visit_Squeel_Nodes_Literal(o, parent)
  Arel.sql(o.expr)
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



123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/squeel/visitors/attribute_visitor.rb', line 123

def visit_Squeel_Nodes_Operation(o, parent)
  args = o.args.map do |arg|
    case arg
    when Nodes::Function, Nodes::KeyPath, Nodes::As, Nodes::Literal
      visit(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



84
85
86
# File 'lib/squeel/visitors/attribute_visitor.rb', line 84

def visit_Squeel_Nodes_Order(o, parent)
  visit(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



181
182
183
184
185
186
187
188
189
190
191
192
193
194
# File 'lib/squeel/visitors/attribute_visitor.rb', line 181

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| visit(val, parent || k)}
  else
    can_visit?(v) ? visit(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.



205
206
207
# File 'lib/squeel/visitors/attribute_visitor.rb', line 205

def visit_without_context_change(k, v, parent)
  v
end