Class: Squeel::Visitors::Visitor
- Inherits:
-
Object
- Object
- Squeel::Visitors::Visitor
- Defined in:
- lib/squeel/visitors/visitor.rb
Overview
The Base visitor class, containing the default behavior common to subclasses.
Direct Known Subclasses
FromVisitor, GroupVisitor, OrderVisitor, PredicateVisitor, PreloadVisitor, SelectVisitor
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
-
#context ⇒ Object
Returns the value of attribute context.
Class Method Summary collapse
-
.can_visit?(object) ⇒ Boolean
Whether or not visitors of this class can visit the given object.
Instance Method Summary collapse
-
#accept(object, parent = context.base) ⇒ Object
Accept an object.
-
#can_visit?(object) ⇒ Boolean
Whether or not the visitor can visit the given object.
-
#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.
-
#implies_hash_context_shift?(v) ⇒ Boolean
private
Whether the given value implies a context change.
-
#initialize(context = nil) ⇒ Visitor
constructor
Create a new Visitor that uses the supplied context object to contextualize visited nodes.
-
#quote(value) ⇒ Arel::Nodes::SqlLiteral
private
Quote a value based on its type, not on the last column used by the ARel visitor.
-
#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).
-
#visit(object, parent) ⇒ Object
private
Visit the object.
-
#visit_ActiveRecord_Base(o, parent) ⇒ Fixnum
private
Visit ActiveRecord::Base objects.
-
#visit_ActiveRecord_Relation(o, parent) ⇒ Arel::SelectManager
private
Visit an ActiveRecord Relation, returning an Arel::SelectManager.
-
#visit_Array(o, parent) ⇒ Array
private
Visit an array, which involves accepting any values we know how to accept, and skipping the rest.
-
#visit_Hash(o, parent) ⇒ Array
private
Visit a Hash.
-
#visit_passthrough(object, parent) ⇒ Object
(also: #visit_Fixnum, #visit_Bignum)
private
Pass an object through the visitor unmodified.
-
#visit_Squeel_Nodes_And(o, parent) ⇒ Arel::Nodes::Grouping
private
Visit a Squeel And node, returning an ARel Grouping containing an ARel And node.
-
#visit_Squeel_Nodes_As(o, parent) ⇒ Arel::Nodes::As
private
Visit a Squeel As node, resulting in am ARel As node.
-
#visit_Squeel_Nodes_Function(o, parent) ⇒ Arel::Nodes::NamedFunction
private
Visit a Squeel function, returning an ARel NamedFunction node.
-
#visit_Squeel_Nodes_Grouping(o, parent) ⇒ Arel::Nodes::Grouping
private
Visit a Squeel Grouping node, returning an ARel Grouping node.
-
#visit_Squeel_Nodes_KeyPath(o, parent) ⇒ Object
private
Visit a keypath.
-
#visit_Squeel_Nodes_Literal(o, parent) ⇒ Arel::Nodes::SqlLiteral
private
Visit a Literal by converting it to an ARel SqlLiteral.
-
#visit_Squeel_Nodes_Not(o, parent) ⇒ Arel::Nodes::Not
private
Visit a Squeel Not node, returning an ARel Not node.
-
#visit_Squeel_Nodes_Operation(o, parent) ⇒ Arel::Nodes::InfixOperation
private
Visit a Squeel operation node, convering it to an ARel InfixOperation (or subclass, as appropriate).
-
#visit_Squeel_Nodes_Or(o, parent) ⇒ Arel::Nodes::Or
private
Visit a Squeel Or node, returning an ARel Or node.
-
#visit_Squeel_Nodes_Stub(o, parent) ⇒ Arel::Attribute
private
Visit a stub.
-
#visit_Symbol(o, parent) ⇒ Arel::Attribute
private
Visit a symbol.
-
#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.
-
#visit_without_hash_context_shift(k, v, parent) ⇒ Object
private
If there is no context change, the default behavior is to return the value unchanged.
Constructor Details
#initialize(context = nil) ⇒ Visitor
Create a new Visitor that uses the supplied context object to contextualize visited nodes.
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
#context ⇒ Object
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.
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.
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.
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.
60 61 62 |
# File 'lib/squeel/visitors/visitor.rb', line 60 def hash_context_shifted? @hash_context_depth > 0 end |
#implies_hash_context_shift?(v) ⇒ Boolean (private)
Returns Whether the given value implies a context change.
66 67 68 |
# File 'lib/squeel/visitors/visitor.rb', line 66 def implies_hash_context_shift?(v) can_visit?(v) 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.
127 128 129 130 131 132 133 134 135 136 137 138 139 140 |
# File 'lib/squeel/visitors/visitor.rb', line 127 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.
111 112 113 114 115 116 117 118 |
# File 'lib/squeel/visitors/visitor.rb', line 111 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.
146 147 148 149 150 151 152 153 154 155 156 157 |
# File 'lib/squeel/visitors/visitor.rb', line 146 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_ActiveRecord_Base(o, parent) ⇒ Fixnum (private)
Visit ActiveRecord::Base objects. These should be converted to their id before being used in a comparison.
370 371 372 |
# File 'lib/squeel/visitors/visitor.rb', line 370 def visit_ActiveRecord_Base(o, parent) o.id end |
#visit_ActiveRecord_Relation(o, parent) ⇒ Arel::SelectManager (private)
Visit an ActiveRecord Relation, returning an Arel::SelectManager
360 361 362 |
# File 'lib/squeel/visitors/visitor.rb', line 360 def visit_ActiveRecord_Relation(o, parent) o.arel end |
#visit_Array(o, parent) ⇒ Array (private)
Visit an array, which involves accepting any values we know how to accept, and skipping the rest.
178 179 180 |
# File 'lib/squeel/visitors/visitor.rb', line 178 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.
188 189 190 191 192 193 194 195 196 |
# File 'lib/squeel/visitors/visitor.rb', line 188 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_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.
166 167 168 |
# File 'lib/squeel/visitors/visitor.rb', line 166 def visit_passthrough(object, parent) object end |
#visit_Squeel_Nodes_And(o, parent) ⇒ Arel::Nodes::Grouping (private)
Visit a Squeel And node, returning an ARel Grouping containing an ARel And node.
266 267 268 |
# File 'lib/squeel/visitors/visitor.rb', line 266 def visit_Squeel_Nodes_And(o, parent) Arel::Nodes::Grouping.new(Arel::Nodes::And.new(visit(o.children, parent))) end |
#visit_Squeel_Nodes_As(o, parent) ⇒ Arel::Nodes::As (private)
Visit a Squeel As node, resulting in am ARel As node.
247 248 249 250 251 252 253 254 255 256 |
# File 'lib/squeel/visitors/visitor.rb', line 247 def visit_Squeel_Nodes_As(o, parent) left = visit(o.left, parent) # Some nodes, like Arel::SelectManager, have their own #as methods, # with behavior that we don't want to clobber. if left.respond_to?(:as) left.as(o.right) else Arel::Nodes::As.new(left, o.right) end end |
#visit_Squeel_Nodes_Function(o, parent) ⇒ Arel::Nodes::NamedFunction (private)
Visit a Squeel function, returning an ARel NamedFunction node.
303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 |
# File 'lib/squeel/visitors/visitor.rb', line 303 def visit_Squeel_Nodes_Function(o, parent) args = o.args.map do |arg| case arg when Nodes::Function, Nodes::As, Nodes::Literal, Nodes::Grouping, Nodes::KeyPath, Nodes::KeyPath visit(arg, parent) when ActiveRecord::Relation arg.arel.ast when Symbol, Nodes::Stub Arel.sql(arel_visitor.accept contextualize(parent)[arg.to_s]) else quote arg end end Arel::Nodes::NamedFunction.new(o.name, args) end |
#visit_Squeel_Nodes_Grouping(o, parent) ⇒ Arel::Nodes::Grouping (private)
Visit a Squeel Grouping node, returning an ARel Grouping node.
293 294 295 |
# File 'lib/squeel/visitors/visitor.rb', line 293 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.
227 228 229 230 231 |
# File 'lib/squeel/visitors/visitor.rb', line 227 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
238 239 240 |
# File 'lib/squeel/visitors/visitor.rb', line 238 def visit_Squeel_Nodes_Literal(o, parent) Arel.sql(o.expr) end |
#visit_Squeel_Nodes_Not(o, parent) ⇒ Arel::Nodes::Not (private)
Visit a Squeel Not node, returning an ARel Not node.
284 285 286 |
# File 'lib/squeel/visitors/visitor.rb', line 284 def visit_Squeel_Nodes_Not(o, parent) Arel::Nodes::Not.new(visit(o.expr, parent)) end |
#visit_Squeel_Nodes_Operation(o, parent) ⇒ Arel::Nodes::InfixOperation (private)
Visit a Squeel operation node, convering it to an ARel InfixOperation (or subclass, as appropriate)
327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 |
# File 'lib/squeel/visitors/visitor.rb', line 327 def visit_Squeel_Nodes_Operation(o, parent) args = o.args.map do |arg| case arg when Nodes::Function, Nodes::As, Nodes::Literal, Nodes::Grouping, Nodes::KeyPath 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::Nodes::InfixOperation.new(o.operator, args[0], args[1]) end op end |
#visit_Squeel_Nodes_Or(o, parent) ⇒ Arel::Nodes::Or (private)
Visit a Squeel Or node, returning an ARel Or node.
275 276 277 |
# File 'lib/squeel/visitors/visitor.rb', line 275 def visit_Squeel_Nodes_Or(o, parent) Arel::Nodes::Grouping.new(Arel::Nodes::Or.new(visit(o.left, parent), (visit(o.right, parent)))) 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.
216 217 218 |
# File 'lib/squeel/visitors/visitor.rb', line 216 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.
205 206 207 |
# File 'lib/squeel/visitors/visitor.rb', line 205 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.
77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
# File 'lib/squeel/visitors/visitor.rb', line 77 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 can_visit?(v) ? visit(v, parent || k) : v ensure @hash_context_depth -= 1 end |
#visit_without_hash_context_shift(k, v, parent) ⇒ Object (private)
If there is no context change, the default behavior is to return the value unchanged. Subclasses will alter this behavior as needed.
99 100 101 |
# File 'lib/squeel/visitors/visitor.rb', line 99 def visit_without_hash_context_shift(k, v, parent) v end |