mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
d017e92e1d
Using a Regexp to replace dynamic segments in a path string is fraught with difficulty and can lead to odd edge cases like #13349. Since we already have a parsed representation of the path it makes sense to use that to generate an array of segments that can be used to build an optimized route's path quickly. Tests on a simple route (e.g. /posts/:id) show a speedup of 35%: https://gist.github.com/pixeltrix/8261932 Calculating ------------------------------------- Current Helper: 5274 i/100ms New Helper: 8050 i/100ms ------------------------------------------------- Current Helper: 79263.6 (±3.7%) i/s - 395550 in 4.997252s New Helper: 153464.5 (±4.9%) i/s - 772800 in 5.047834s Tests on a more complex route show even an greater performance boost: https://gist.github.com/pixeltrix/8261957 Calculating ------------------------------------- Current Helper: 2367 i/100ms New Helper: 5382 i/100ms ------------------------------------------------- Current Helper: 29506.0 (±3.2%) i/s - 149121 in 5.059294s New Helper: 78815.5 (±4.1%) i/s - 398268 in 5.062161s It also has the added benefit of fixing the edge cases described above. Fixes #13349
219 lines
4.7 KiB
Ruby
219 lines
4.7 KiB
Ruby
# encoding: utf-8
|
|
|
|
require 'thread_safe'
|
|
|
|
module ActionDispatch
|
|
module Journey # :nodoc:
|
|
module Visitors # :nodoc:
|
|
class Visitor # :nodoc:
|
|
DISPATCH_CACHE = ThreadSafe::Cache.new { |h,k|
|
|
h[k] = :"visit_#{k}"
|
|
}
|
|
|
|
def accept(node)
|
|
visit(node)
|
|
end
|
|
|
|
private
|
|
|
|
def visit node
|
|
send(DISPATCH_CACHE[node.type], node)
|
|
end
|
|
|
|
def binary(node)
|
|
visit(node.left)
|
|
visit(node.right)
|
|
end
|
|
def visit_CAT(n); binary(n); end
|
|
|
|
def nary(node)
|
|
node.children.each { |c| visit(c) }
|
|
end
|
|
def visit_OR(n); nary(n); end
|
|
|
|
def unary(node)
|
|
visit(node.left)
|
|
end
|
|
def visit_GROUP(n); unary(n); end
|
|
def visit_STAR(n); unary(n); end
|
|
|
|
def terminal(node); end
|
|
%w{ LITERAL SYMBOL SLASH DOT }.each do |t|
|
|
class_eval %{ def visit_#{t}(n); terminal(n); end }, __FILE__, __LINE__
|
|
end
|
|
end
|
|
|
|
# Loop through the requirements AST
|
|
class Each < Visitor # :nodoc:
|
|
attr_reader :block
|
|
|
|
def initialize(block)
|
|
@block = block
|
|
end
|
|
|
|
def visit(node)
|
|
super
|
|
block.call(node)
|
|
end
|
|
end
|
|
|
|
class String < Visitor # :nodoc:
|
|
private
|
|
|
|
def binary(node)
|
|
[visit(node.left), visit(node.right)].join
|
|
end
|
|
|
|
def nary(node)
|
|
node.children.map { |c| visit(c) }.join '|'
|
|
end
|
|
|
|
def terminal(node)
|
|
node.left
|
|
end
|
|
|
|
def visit_GROUP(node)
|
|
"(#{visit(node.left)})"
|
|
end
|
|
end
|
|
|
|
class OptimizedPath < Visitor # :nodoc:
|
|
def accept(node)
|
|
Array(visit(node))
|
|
end
|
|
|
|
private
|
|
|
|
def visit_CAT(node)
|
|
[visit(node.left), visit(node.right)].flatten
|
|
end
|
|
|
|
def visit_SYMBOL(node)
|
|
node.left[1..-1].to_sym
|
|
end
|
|
|
|
def visit_STAR(node)
|
|
visit(node.left)
|
|
end
|
|
|
|
def visit_GROUP(node)
|
|
[]
|
|
end
|
|
|
|
%w{ LITERAL SLASH DOT }.each do |t|
|
|
class_eval %{ def visit_#{t}(n); n.left; end }, __FILE__, __LINE__
|
|
end
|
|
end
|
|
|
|
# Used for formatting urls (url_for)
|
|
class Formatter < Visitor # :nodoc:
|
|
attr_reader :options
|
|
|
|
def initialize(options)
|
|
@options = options
|
|
end
|
|
|
|
private
|
|
|
|
def visit(node, optional = false)
|
|
case node.type
|
|
when :LITERAL, :SLASH, :DOT
|
|
node.left
|
|
when :STAR
|
|
visit(node.left)
|
|
when :GROUP
|
|
visit(node.left, true)
|
|
when :CAT
|
|
visit_CAT(node, optional)
|
|
when :SYMBOL
|
|
visit_SYMBOL(node)
|
|
end
|
|
end
|
|
|
|
def visit_CAT(node, optional)
|
|
left = visit(node.left, optional)
|
|
right = visit(node.right, optional)
|
|
|
|
if optional && !(right && left)
|
|
""
|
|
else
|
|
[left, right].join
|
|
end
|
|
end
|
|
|
|
def visit_SYMBOL(node)
|
|
if value = options[node.to_sym]
|
|
Router::Utils.escape_path(value)
|
|
end
|
|
end
|
|
end
|
|
|
|
class Dot < Visitor # :nodoc:
|
|
def initialize
|
|
@nodes = []
|
|
@edges = []
|
|
end
|
|
|
|
def accept(node)
|
|
super
|
|
<<-eodot
|
|
digraph parse_tree {
|
|
size="8,5"
|
|
node [shape = none];
|
|
edge [dir = none];
|
|
#{@nodes.join "\n"}
|
|
#{@edges.join("\n")}
|
|
}
|
|
eodot
|
|
end
|
|
|
|
private
|
|
|
|
def binary(node)
|
|
node.children.each do |c|
|
|
@edges << "#{node.object_id} -> #{c.object_id};"
|
|
end
|
|
super
|
|
end
|
|
|
|
def nary(node)
|
|
node.children.each do |c|
|
|
@edges << "#{node.object_id} -> #{c.object_id};"
|
|
end
|
|
super
|
|
end
|
|
|
|
def unary(node)
|
|
@edges << "#{node.object_id} -> #{node.left.object_id};"
|
|
super
|
|
end
|
|
|
|
def visit_GROUP(node)
|
|
@nodes << "#{node.object_id} [label=\"()\"];"
|
|
super
|
|
end
|
|
|
|
def visit_CAT(node)
|
|
@nodes << "#{node.object_id} [label=\"○\"];"
|
|
super
|
|
end
|
|
|
|
def visit_STAR(node)
|
|
@nodes << "#{node.object_id} [label=\"*\"];"
|
|
super
|
|
end
|
|
|
|
def visit_OR(node)
|
|
@nodes << "#{node.object_id} [label=\"|\"];"
|
|
super
|
|
end
|
|
|
|
def terminal(node)
|
|
value = node.left
|
|
|
|
@nodes << "#{node.object_id} [label=\"#{value}\"];"
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|