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).
- #symbolify(o) ⇒ Object private
-
#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 Active Record 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.
69 70 71 |
# File 'lib/squeel/visitors/visitor.rb', line 69 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.
75 76 77 |
# File 'lib/squeel/visitors/visitor.rb', line 75 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.
137 138 139 140 141 142 143 144 145 146 147 148 149 150 |
# File 'lib/squeel/visitors/visitor.rb', line 137 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.
120 121 122 123 124 125 126 127 128 |
# File 'lib/squeel/visitors/visitor.rb', line 120 def quoted?(object) case object when NilClass, Arel::Nodes::SqlLiteral, Bignum, Fixnum, Arel::SelectManager false else true end end |
#symbolify(o) ⇒ Object (private)
49 50 51 52 53 54 55 56 |
# File 'lib/squeel/visitors/visitor.rb', line 49 def symbolify(o) case o when Symbol, String, Nodes::Stub o.to_sym else nil end end |
#visit(object, parent) ⇒ Object (private)
Visit the object.
156 157 158 159 160 161 162 163 164 165 166 167 |
# File 'lib/squeel/visitors/visitor.rb', line 156 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.
380 381 382 |
# File 'lib/squeel/visitors/visitor.rb', line 380 def visit_ActiveRecord_Base(o, parent) o.id end |
#visit_ActiveRecord_Relation(o, parent) ⇒ Arel::SelectManager (private)
Visit an Active Record Relation, returning an Arel::SelectManager
370 371 372 |
# File 'lib/squeel/visitors/visitor.rb', line 370 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.
188 189 190 |
# File 'lib/squeel/visitors/visitor.rb', line 188 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.
198 199 200 201 202 203 204 205 206 |
# File 'lib/squeel/visitors/visitor.rb', line 198 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.
176 177 178 |
# File 'lib/squeel/visitors/visitor.rb', line 176 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.
276 277 278 |
# File 'lib/squeel/visitors/visitor.rb', line 276 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.
257 258 259 260 261 262 263 264 265 266 |
# File 'lib/squeel/visitors/visitor.rb', line 257 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.
313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 |
# File 'lib/squeel/visitors/visitor.rb', line 313 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.function_name, args) end |
#visit_Squeel_Nodes_Grouping(o, parent) ⇒ Arel::Nodes::Grouping (private)
Visit a Squeel Grouping node, returning an Arel Grouping node.
303 304 305 |
# File 'lib/squeel/visitors/visitor.rb', line 303 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.
237 238 239 240 241 |
# File 'lib/squeel/visitors/visitor.rb', line 237 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
248 249 250 |
# File 'lib/squeel/visitors/visitor.rb', line 248 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.
294 295 296 |
# File 'lib/squeel/visitors/visitor.rb', line 294 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)
337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 |
# File 'lib/squeel/visitors/visitor.rb', line 337 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.
285 286 287 |
# File 'lib/squeel/visitors/visitor.rb', line 285 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.
226 227 228 |
# File 'lib/squeel/visitors/visitor.rb', line 226 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.
215 216 217 |
# File 'lib/squeel/visitors/visitor.rb', line 215 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.
86 87 88 89 90 91 92 93 94 95 96 97 98 99 |
# File 'lib/squeel/visitors/visitor.rb', line 86 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.
108 109 110 |
# File 'lib/squeel/visitors/visitor.rb', line 108 def visit_without_hash_context_shift(k, v, parent) v end |