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



29
30
31
# File 'lib/squeel/visitors/attribute_visitor.rb', line 29

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



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

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_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



133
134
135
136
137
138
139
140
141
142
143
144
145
# File 'lib/squeel/visitors/attribute_visitor.rb', line 133

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.



105
106
107
108
109
# File 'lib/squeel/visitors/attribute_visitor.rb', line 105

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



153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# File 'lib/squeel/visitors/attribute_visitor.rb', line 153

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



116
117
118
# File 'lib/squeel/visitors/attribute_visitor.rb', line 116

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



94
95
96
# File 'lib/squeel/visitors/attribute_visitor.rb', line 94

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



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

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



40
41
42
43
44
45
46
47
48
49
50
51
52
53
# File 'lib/squeel/visitors/attribute_visitor.rb', line 40

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.



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

def visit_without_context_change(k, v, parent)
  v
end