This commit introduces a functional Path AST visitor and implements
`each` on the AST in terms of the functional visitor. The functional
visitor doesn't maintain state, so we only need to allocate one of them.
Given this benchmark route file:
```ruby
require 'action_pack'
require 'action_dispatch'
route_set = ActionDispatch::Routing::RouteSet.new
routes = ActionDispatch::Routing::Mapper.new route_set
ObjectSpace::AllocationTracer.setup(%i{path line type})
result = ObjectSpace::AllocationTracer.trace do
500.times{|i|
routes.resource :omglol
}
end
result.find_all { |k,v| k.first =~ /git\/rails/ }.sort_by { |k,v|
v.first
}.each { |k,v|
p k => v
}
```
node.rb line 17 was in our top 3 allocation spot:
```
{["/Users/aaron/git/rails/actionpack/lib/action_dispatch/journey/nodes/node.rb", 17, :T_OBJECT]=>[31526, 0, 28329, 0, 2, 1123160]}
{["/Users/aaron/git/rails/actionpack/lib/action_dispatch/routing/mapper.rb", 2080, :T_IMEMO]=>[34002, 0, 30563, 0, 2, 1211480]}
{["/Users/aaron/git/rails/actionpack/lib/action_dispatch/routing/mapper.rb", 2071, :T_IMEMO]=>[121934, 1, 109608, 0, 7, 4344400]}
```
This commit eliminates allocations at that place.
This reverts commit 5c224de9e1.
Conflicts:
actionpack/lib/action_dispatch/journey/visitors.rb
5c224de9e1 introduced a bug in the
formatter. This commit includes a regression test.
1. Escape '%' characters in URLs - only unescaped data
should be passed to URL helpers
2. Add an `escape_segment` helper to `Router::Utils`
that escapes '/' characters
3. Use `escape_segment` rather than `escape_fragment`
in optimized URL generation
4. Use `escape_segment` rather than `escape_path`
in URL generation
For point 4 there are two exceptions. Firstly, when a route uses wildcard
segments (e.g. *foo) then we use `escape_path` as the value may contain '/'
characters. This means that wildcard routes can't be optimized. Secondly,
if a `:controller` segment is used in the path then this uses `escape_path`
as the controller may be namespaced.
Fixes#14629, #14636 and #14070.
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
Rather than trying to use gsub to remove the optional route segments,
which will fail with nested optional segments, use a custom visitor
class that returns a empty string for group nodes.
Closes#9524
Move the Journey code underneath the ActionDispatch namespace so
that we don't pollute the global namespace with names that may
be used for models.
Fixesrails/journey#49.