Class: Squeel::Visitors::AttributeVisitor

Inherits:
Visitor
  • 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 Visitor

Visitor::DISPATCH

Instance Attribute Summary

Attributes inherited from Visitor

#context

Instance Method Summary collapse

Methods inherited from Visitor

#accept, #can_visit?, can_visit?, #hash_context_shifted?, #initialize, #quote, #quoted?, #visit, #visit_Array, #visit_passthrough

Constructor Details

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

Instance Method Details

#implies_hash_context_shift?(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



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

def implies_hash_context_shift?(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



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

def visit_ActiveRecord_Relation(o, parent)
  o.arel
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_hash_context_shift?(v)
      visit_with_hash_context_shift(k, v, parent)
    else
      visit_without_hash_context_shift(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.



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

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



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

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

  o.alias ? func.as(o.alias) : func
end

#visit_Squeel_Nodes_Grouping(o, parent) ⇒ Arel::Nodes::Grouping (private)

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

Parameters:

  • The (Nodes::Grouping)

    Grouping node to visit

  • parent

    The parent object in the context

Returns:

  • (Arel::Nodes::Grouping)

    The resulting grouping node.



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

def visit_Squeel_Nodes_Grouping(o, parent)
  Arel::Nodes::Grouping.new(visit(o.expr, parent))
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.



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

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



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

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



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
138
# File 'lib/squeel/visitors/attribute_visitor.rb', line 113

def visit_Squeel_Nodes_Operation(o, parent)
  args = o.args.map do |arg|
    case arg
    when Nodes::Function, Nodes::KeyPath, Nodes::As, Nodes::Literal, Nodes::Grouping
      visit(arg, parent)
    when Symbol, Nodes::Stub
      Arel.sql(arel_visitor.accept contextualize(parent)[arg.to_s])
    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



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

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



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

def visit_Squeel_Nodes_Stub(o, parent)
  contextualize(parent)[o.to_s]
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



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

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

#visit_with_hash_context_shift(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



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

def visit_with_hash_context_shift(k, v, parent)
  @hash_context_depth += 1

  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
ensure
  @hash_context_depth -= 1
end

#visit_without_hash_context_shift(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.



208
209
210
# File 'lib/squeel/visitors/attribute_visitor.rb', line 208

def visit_without_hash_context_shift(k, v, parent)
  v
end