mirror of https://github.com/rails/rails
Merge pull request #5242 from rails/opt_routes
Optimize routes generation in simple cases. If you pass to the route helper the same amount of arguments as the required segments, route generation will be optimized as a string interpolation. After this commit, `post_path(post)` is about 6.5 times faster, `post_url(post)` is about 5 times.
This commit is contained in:
commit
1a046ab9bf
|
@ -26,22 +26,20 @@ module ActionController
|
||||||
include AbstractController::UrlFor
|
include AbstractController::UrlFor
|
||||||
|
|
||||||
def url_options
|
def url_options
|
||||||
@_url_options ||= super.reverse_merge(
|
@_url_options ||= begin
|
||||||
:host => request.host,
|
hash = super.reverse_merge(
|
||||||
:port => request.optional_port,
|
:host => request.host,
|
||||||
:protocol => request.protocol,
|
:port => request.optional_port,
|
||||||
:_path_segments => request.symbolized_path_parameters
|
:protocol => request.protocol,
|
||||||
).freeze
|
:_path_segments => request.symbolized_path_parameters
|
||||||
|
)
|
||||||
|
|
||||||
if _routes.equal?(env["action_dispatch.routes"])
|
if _routes.equal?(env["action_dispatch.routes"])
|
||||||
@_url_options.dup.tap do |options|
|
hash[:script_name] = request.script_name.dup
|
||||||
options[:script_name] = request.script_name.dup
|
|
||||||
options.freeze
|
|
||||||
end
|
end
|
||||||
else
|
|
||||||
@_url_options
|
hash.freeze
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -40,7 +40,9 @@ module ActionDispatch
|
||||||
rewritten_url << ":#{options.delete(:port)}" if options[:port]
|
rewritten_url << ":#{options.delete(:port)}" if options[:port]
|
||||||
end
|
end
|
||||||
|
|
||||||
path = options.delete(:path) || ''
|
path = ""
|
||||||
|
path << options.delete(:script_name).to_s.chomp("/")
|
||||||
|
path << options.delete(:path).to_s
|
||||||
|
|
||||||
params = options[:params] || {}
|
params = options[:params] || {}
|
||||||
params.reject! {|k,v| v.to_param.nil? }
|
params.reject! {|k,v| v.to_param.nil? }
|
||||||
|
|
|
@ -446,7 +446,11 @@ module ActionDispatch
|
||||||
_route = @set.named_routes.routes[name.to_sym]
|
_route = @set.named_routes.routes[name.to_sym]
|
||||||
_routes = @set
|
_routes = @set
|
||||||
app.routes.define_mounted_helper(name)
|
app.routes.define_mounted_helper(name)
|
||||||
app.routes.class_eval do
|
app.routes.singleton_class.class_eval do
|
||||||
|
define_method :mounted? do
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
define_method :_generate_prefix do |options|
|
define_method :_generate_prefix do |options|
|
||||||
prefix_options = options.slice(*_route.segment_keys)
|
prefix_options = options.slice(*_route.segment_keys)
|
||||||
# we must actually delete prefix segment keys to avoid passing them to next url_for
|
# we must actually delete prefix segment keys to avoid passing them to next url_for
|
||||||
|
|
|
@ -191,14 +191,50 @@ module ActionDispatch
|
||||||
selector = url_helper_name(name, kind)
|
selector = url_helper_name(name, kind)
|
||||||
hash_access_method = hash_access_name(name, kind)
|
hash_access_method = hash_access_name(name, kind)
|
||||||
|
|
||||||
@module.module_eval <<-END_EVAL, __FILE__, __LINE__ + 1
|
if optimize_helper?(kind, route)
|
||||||
remove_possible_method :#{selector}
|
@module.module_eval <<-END_EVAL, __FILE__, __LINE__ + 1
|
||||||
def #{selector}(*args)
|
remove_possible_method :#{selector}
|
||||||
url_for(#{hash_access_method}(*args))
|
def #{selector}(*args)
|
||||||
end
|
if args.size == #{route.required_parts.size} && !args.last.is_a?(Hash) && optimize_routes_generation?
|
||||||
END_EVAL
|
options = #{options.inspect}.merge!(url_options)
|
||||||
|
options[:path] = "#{optimized_helper(route)}"
|
||||||
|
ActionDispatch::Http::URL.url_for(options)
|
||||||
|
else
|
||||||
|
url_for(#{hash_access_method}(*args))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
END_EVAL
|
||||||
|
else
|
||||||
|
@module.module_eval <<-END_EVAL, __FILE__, __LINE__ + 1
|
||||||
|
remove_possible_method :#{selector}
|
||||||
|
def #{selector}(*args)
|
||||||
|
url_for(#{hash_access_method}(*args))
|
||||||
|
end
|
||||||
|
END_EVAL
|
||||||
|
end
|
||||||
|
|
||||||
helpers << selector
|
helpers << selector
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Clause check about when we need to generate an optimized helper.
|
||||||
|
def optimize_helper?(kind, route) #:nodoc:
|
||||||
|
route.ast.grep(Journey::Nodes::Star).empty? && route.requirements.except(:controller, :action).empty?
|
||||||
|
end
|
||||||
|
|
||||||
|
# Generates the interpolation to be used in the optimized helper.
|
||||||
|
def optimized_helper(route)
|
||||||
|
string_route = route.ast.to_s
|
||||||
|
|
||||||
|
while string_route.gsub!(/\([^\)]*\)/, "")
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
route.required_parts.each_with_index do |part, i|
|
||||||
|
string_route.gsub!(part.inspect, "\#{Journey::Router::Utils.escape_fragment(args[#{i}].to_param)}")
|
||||||
|
end
|
||||||
|
|
||||||
|
string_route
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
attr_accessor :formatter, :set, :named_routes, :default_scope, :router
|
attr_accessor :formatter, :set, :named_routes, :default_scope, :router
|
||||||
|
@ -323,7 +359,7 @@ module ActionDispatch
|
||||||
# Rails.application.routes.url_helpers.url_for(args)
|
# Rails.application.routes.url_helpers.url_for(args)
|
||||||
@_routes = routes
|
@_routes = routes
|
||||||
class << self
|
class << self
|
||||||
delegate :url_for, :to => '@_routes'
|
delegate :url_for, :optimize_routes_generation?, :to => '@_routes'
|
||||||
end
|
end
|
||||||
|
|
||||||
# Make named_routes available in the module singleton
|
# Make named_routes available in the module singleton
|
||||||
|
@ -557,6 +593,14 @@ module ActionDispatch
|
||||||
RESERVED_OPTIONS = [:host, :protocol, :port, :subdomain, :domain, :tld_length,
|
RESERVED_OPTIONS = [:host, :protocol, :port, :subdomain, :domain, :tld_length,
|
||||||
:trailing_slash, :anchor, :params, :only_path, :script_name]
|
:trailing_slash, :anchor, :params, :only_path, :script_name]
|
||||||
|
|
||||||
|
def mounted?
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
|
def optimize_routes_generation?
|
||||||
|
!mounted? && default_url_options.empty?
|
||||||
|
end
|
||||||
|
|
||||||
def _generate_prefix(options = {})
|
def _generate_prefix(options = {})
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
|
@ -568,19 +612,17 @@ module ActionDispatch
|
||||||
|
|
||||||
user, password = extract_authentication(options)
|
user, password = extract_authentication(options)
|
||||||
path_segments = options.delete(:_path_segments)
|
path_segments = options.delete(:_path_segments)
|
||||||
script_name = options.delete(:script_name)
|
script_name = options.delete(:script_name).presence || _generate_prefix(options)
|
||||||
|
|
||||||
path = (script_name.blank? ? _generate_prefix(options) : script_name.chomp('/')).to_s
|
|
||||||
|
|
||||||
path_options = options.except(*RESERVED_OPTIONS)
|
path_options = options.except(*RESERVED_OPTIONS)
|
||||||
path_options = yield(path_options) if block_given?
|
path_options = yield(path_options) if block_given?
|
||||||
|
|
||||||
path_addition, params = generate(path_options, path_segments || {})
|
path, params = generate(path_options, path_segments || {})
|
||||||
path << path_addition
|
|
||||||
params.merge!(options[:params] || {})
|
params.merge!(options[:params] || {})
|
||||||
|
|
||||||
ActionDispatch::Http::URL.url_for(options.merge!({
|
ActionDispatch::Http::URL.url_for(options.merge!({
|
||||||
:path => path,
|
:path => path,
|
||||||
|
:script_name => script_name,
|
||||||
:params => params,
|
:params => params,
|
||||||
:user => user,
|
:user => user,
|
||||||
:password => password
|
:password => password
|
||||||
|
|
|
@ -102,6 +102,9 @@ module ActionDispatch
|
||||||
super
|
super
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Hook overriden in controller to add request information
|
||||||
|
# with `default_url_options`. Application logic should not
|
||||||
|
# go into url_options.
|
||||||
def url_options
|
def url_options
|
||||||
default_url_options
|
default_url_options
|
||||||
end
|
end
|
||||||
|
@ -152,6 +155,11 @@ module ActionDispatch
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
||||||
|
def optimize_routes_generation?
|
||||||
|
return @_optimized_routes if defined?(@_optimized_routes)
|
||||||
|
@_optimized_routes = _routes.optimize_routes_generation? && default_url_options.empty?
|
||||||
|
end
|
||||||
|
|
||||||
def _with_routes(routes)
|
def _with_routes(routes)
|
||||||
old_routes, @_routes = @_routes, routes
|
old_routes, @_routes = @_routes, routes
|
||||||
yield
|
yield
|
||||||
|
|
|
@ -23,20 +23,25 @@ module ActionView
|
||||||
include ActionDispatch::Routing::UrlFor
|
include ActionDispatch::Routing::UrlFor
|
||||||
include TagHelper
|
include TagHelper
|
||||||
|
|
||||||
def _routes_context
|
# We need to override url_optoins, _routes_context
|
||||||
controller
|
# and optimize_routes_generation? to consider the controller.
|
||||||
end
|
|
||||||
|
|
||||||
# Need to map default url options to controller one.
|
def url_options #:nodoc:
|
||||||
# def default_url_options(*args) #:nodoc:
|
|
||||||
# controller.send(:default_url_options, *args)
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
def url_options
|
|
||||||
return super unless controller.respond_to?(:url_options)
|
return super unless controller.respond_to?(:url_options)
|
||||||
controller.url_options
|
controller.url_options
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def _routes_context #:nodoc:
|
||||||
|
controller
|
||||||
|
end
|
||||||
|
protected :_routes_context
|
||||||
|
|
||||||
|
def optimize_routes_generation? #:nodoc:
|
||||||
|
controller.respond_to?(:optimize_routes_generation?) ?
|
||||||
|
controller.optimize_routes_generation? : super
|
||||||
|
end
|
||||||
|
protected :optimize_routes_generation?
|
||||||
|
|
||||||
# Returns the URL for the set of +options+ provided. This takes the
|
# Returns the URL for the set of +options+ provided. This takes the
|
||||||
# same options as +url_for+ in Action Controller (see the
|
# same options as +url_for+ in Action Controller (see the
|
||||||
# documentation for <tt>ActionController::Base#url_for</tt>). Note that by default
|
# documentation for <tt>ActionController::Base#url_for</tt>). Note that by default
|
||||||
|
|
|
@ -56,7 +56,7 @@ class UrlOptionsController < ActionController::Base
|
||||||
end
|
end
|
||||||
|
|
||||||
def url_options
|
def url_options
|
||||||
super.merge(:host => 'www.override.com', :action => 'new', :locale => 'en')
|
super.merge(:host => 'www.override.com')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -183,9 +183,9 @@ class UrlOptionsTest < ActionController::TestCase
|
||||||
|
|
||||||
get :from_view, :route => "from_view_url"
|
get :from_view, :route => "from_view_url"
|
||||||
|
|
||||||
assert_equal 'http://www.override.com/from_view?locale=en', @response.body
|
assert_equal 'http://www.override.com/from_view', @response.body
|
||||||
assert_equal 'http://www.override.com/from_view?locale=en', @controller.send(:from_view_url)
|
assert_equal 'http://www.override.com/from_view', @controller.send(:from_view_url)
|
||||||
assert_equal 'http://www.override.com/default_url_options/new?locale=en', @controller.url_for(:controller => 'default_url_options')
|
assert_equal 'http://www.override.com/default_url_options/index', @controller.url_for(:controller => 'default_url_options')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -59,11 +59,11 @@ end
|
||||||
class MockController
|
class MockController
|
||||||
def self.build(helpers)
|
def self.build(helpers)
|
||||||
Class.new do
|
Class.new do
|
||||||
def url_for(options)
|
def url_options
|
||||||
|
options = super
|
||||||
options[:protocol] ||= "http"
|
options[:protocol] ||= "http"
|
||||||
options[:host] ||= "test.host"
|
options[:host] ||= "test.host"
|
||||||
|
options
|
||||||
super(options)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
include helpers
|
include helpers
|
||||||
|
|
|
@ -17,7 +17,7 @@ class SprocketsHelperWithRoutesTest < ActionView::TestCase
|
||||||
|
|
||||||
def setup
|
def setup
|
||||||
super
|
super
|
||||||
@controller = BasicController.new
|
@controller = BasicController.new
|
||||||
|
|
||||||
@assets = Sprockets::Environment.new
|
@assets = Sprockets::Environment.new
|
||||||
@assets.append_path(FIXTURES.join("sprockets/app/javascripts"))
|
@assets.append_path(FIXTURES.join("sprockets/app/javascripts"))
|
||||||
|
@ -34,7 +34,7 @@ class SprocketsHelperWithRoutesTest < ActionView::TestCase
|
||||||
|
|
||||||
test "namespace conflicts on a named route called asset_path" do
|
test "namespace conflicts on a named route called asset_path" do
|
||||||
# Testing this for sanity - asset_path is now a named route!
|
# Testing this for sanity - asset_path is now a named route!
|
||||||
assert_match asset_path('test_asset'), '/assets/test_asset'
|
assert_equal asset_path('test_asset'), '/assets/test_asset'
|
||||||
|
|
||||||
assert_match %r{/assets/logo-[0-9a-f]+.png},
|
assert_match %r{/assets/logo-[0-9a-f]+.png},
|
||||||
path_to_asset("logo.png")
|
path_to_asset("logo.png")
|
||||||
|
|
Loading…
Reference in New Issue