Class: Squeel::Visitors::Visitor

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

Overview

The Base visitor class, containing the default behavior common to subclasses.

Direct Known Subclasses

AttributeVisitor, PredicateVisitor, SymbolVisitor

Constant Summary collapse

DISPATCH =

A hash that caches the method name to use for a visitor for a given class

Hash.new do |hash, klass|
  hash[klass] = "visit_#{(klass.name || '').gsub('::', '_')}"
end

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(context = nil) ⇒ Visitor

Create a new Visitor that uses the supplied context object to contextualize visited nodes.

Parameters:

  • context (Context) (defaults to: nil)

    The context to use for node visitation.



15
16
17
18
# File 'lib/squeel/visitors/visitor.rb', line 15

def initialize(context = nil)
  @context = context
  @hash_context_depth = 0
end

Instance Attribute Details

#contextObject

Returns the value of attribute context.



8
9
10
# File 'lib/squeel/visitors/visitor.rb', line 8

def context
  @context
end

Class Method Details

.can_visit?(object) ⇒ Boolean

Returns Whether or not visitors of this class can visit the given object.

Parameters:

  • object

    The object to check

Returns:

  • (Boolean)

    Whether or not visitors of this class can visit the given object



38
39
40
41
42
43
44
45
# File 'lib/squeel/visitors/visitor.rb', line 38

def self.can_visit?(object)
  @can_visit ||= Hash.new do |hash, klass|
    hash[klass] = klass.ancestors.detect { |ancestor|
      private_method_defined? DISPATCH[ancestor]
    } ? true : false
  end
  @can_visit[object.class]
end

Instance Method Details

#accept(object, parent = context.base) ⇒ Object

Accept an object.

Parameters:

  • object

    The object to visit

  • parent (defaults to: context.base)

    The parent of this object, to track the object’s place in any association hierarchy.

Returns:

  • The results of the node visitation, typically an ARel object of some kind.



26
27
28
# File 'lib/squeel/visitors/visitor.rb', line 26

def accept(object, parent = context.base)
  visit(object, parent)
end

#can_visit?(object) ⇒ Boolean

Returns Whether or not the visitor can visit the given object.

Parameters:

  • object

    The object to check

Returns:

  • (Boolean)

    Whether or not the visitor can visit the given object



32
33
34
# File 'lib/squeel/visitors/visitor.rb', line 32

def can_visit?(object)
  self.class.can_visit? object
end

#hash_context_shifted?Boolean (private)

If we’re visiting stuff in a hash, it’s good to check whether or not we’ve shifted context already. If we have, we may want to use caution as it pertains to certain input, in case it’s untrusted. See CVE-2012-2661 for info.

Returns:

  • (Boolean)

    Whether we’re within a new context.



60
61
62
# File 'lib/squeel/visitors/visitor.rb', line 60

def hash_context_shifted?
  @hash_context_depth > 0
end

#quote(value) ⇒ Arel::Nodes::SqlLiteral (private)

Quote a value based on its type, not on the last column used by the ARel visitor. This is occasionally necessary to avoid having ARel quote a value according to an integer column, converting ‘My String’ to 0.

Parameters:

  • value

    The value to quote

Returns:

  • (Arel::Nodes::SqlLiteral)

    if the value needs to be pre-quoted

  • the unquoted value, if default quoting won’t hurt.



88
89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/squeel/visitors/visitor.rb', line 88

def quote(value)
  if quoted? value
    case value
    when Array
      value.map {|v| quote(v)}
    when Range
      Range.new(quote(value.begin), quote(value.end), value.exclude_end?)
    else
      Arel.sql(arel_visitor.accept value)
    end
  else
    value
  end
end

#quoted?(object) ⇒ Boolean (private)

Important to avoid accidentally allowing the default ARel visitor’s last_column quoting behavior (where a value is quoted as though it is of the type of the last visited column). This can wreak havoc with Functions and Operations.

Parameters:

  • object

    The object to check

Returns:

  • (Boolean)

    Whether or not the ARel visitor will try to quote the object if not passed as an SqlLiteral.



72
73
74
75
76
77
78
79
# File 'lib/squeel/visitors/visitor.rb', line 72

def quoted?(object)
  case object
  when Arel::Nodes::SqlLiteral, Bignum, Fixnum, Arel::SelectManager
    false
  else
    true
  end
end

#visit(object, parent) ⇒ Object (private)

Visit the object.

Parameters:

  • object

    The object to visit

  • parent

    The object’s parent within the context



107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/squeel/visitors/visitor.rb', line 107

def visit(object, parent)
  send(DISPATCH[object.class], object, parent)
rescue NoMethodError => e
  raise e if respond_to?(DISPATCH[object.class], true)

  superklass = object.class.ancestors.find { |klass|
    respond_to?(DISPATCH[klass], true)
  }
  raise(TypeError, "Cannot visit #{object.class}") unless superklass
  DISPATCH[object.class] = DISPATCH[superklass]
  retry
end

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

Visit an array, which involves accepting any values we know how to accept, and skipping the rest.

Parameters:

  • o (Array)

    The Array to visit

  • parent

    The current parent object in the context

Returns:

  • (Array)

    The visited array



126
127
128
# File 'lib/squeel/visitors/visitor.rb', line 126

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

#visit_passthrough(object, parent) ⇒ Object (private) Also known as: visit_Fixnum, visit_Bignum

Pass an object through the visitor unmodified. This is in order to allow objects that don’t require modification to be handled by ARel directly.

Parameters:

  • object

    The object to visit

  • parent

    The object’s parent within the context

Returns:

  • The object, unmodified



137
138
139
# File 'lib/squeel/visitors/visitor.rb', line 137

def visit_passthrough(object, parent)
  object
end