Skip to content

Commit

Permalink
Optimize path helpers.
Browse files Browse the repository at this point in the history
  • Loading branch information
josevalim committed Mar 2, 2012
1 parent fcef728 commit d7014bc
Show file tree
Hide file tree
Showing 5 changed files with 81 additions and 29 deletions.
30 changes: 14 additions & 16 deletions actionpack/lib/action_controller/metal/url_for.rb
@@ -1,7 +1,7 @@
# Includes +url_for+ into the host class. The class has to provide a +RouteSet+ by implementing
# Includes +url_for+ into the host class. The class has to provide a +RouteSet+ by implementing
# the <tt>_routes</tt> method. Otherwise, an exception will be raised.
#
# In addition to <tt>AbstractController::UrlFor</tt>, this module accesses the HTTP layer to define
# In addition to <tt>AbstractController::UrlFor</tt>, this module accesses the HTTP layer to define
# url options like the +host+. In order to do so, this module requires the host class
# to implement +env+ and +request+, which need to be a Rack-compatible.
#
Expand All @@ -18,30 +18,28 @@
# @url = root_path # named route from the application.
# end
# end
#
#
module ActionController
module UrlFor
extend ActiveSupport::Concern

include AbstractController::UrlFor

def url_options
@_url_options ||= super.reverse_merge(
:host => request.host,
:port => request.optional_port,
:protocol => request.protocol,
:_path_segments => request.symbolized_path_parameters
).freeze
@_url_options ||= begin
hash = super.reverse_merge(
:host => request.host,
:port => request.optional_port,
:protocol => request.protocol,
:_path_segments => request.symbolized_path_parameters
)

if _routes.equal?(env["action_dispatch.routes"])
@_url_options.dup.tap do |options|
options[:script_name] = request.script_name.dup
options.freeze
if _routes.equal?(env["action_dispatch.routes"])
hash[:script_name] = request.script_name.dup
end
else
@_url_options

hash.freeze
end
end

end
end
4 changes: 3 additions & 1 deletion actionpack/lib/action_dispatch/http/url.rb
Expand Up @@ -40,7 +40,9 @@ def url_for(options = {})
rewritten_url << ":#{options.delete(:port)}" if options[:port]
end

path = options.delete(:path) || ''
path = ""
path << options.delete(:script_name).to_s.chomp("/")
path << options.delete(:path).to_s

params = options[:params] || {}
params.reject! {|k,v| v.to_param.nil? }
Expand Down
6 changes: 5 additions & 1 deletion actionpack/lib/action_dispatch/routing/mapper.rb
Expand Up @@ -446,7 +446,11 @@ def define_generate_prefix(app, name)
_route = @set.named_routes.routes[name.to_sym]
_routes = @set
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|
prefix_options = options.slice(*_route.segment_keys)
# we must actually delete prefix segment keys to avoid passing them to next url_for
Expand Down
65 changes: 54 additions & 11 deletions actionpack/lib/action_dispatch/routing/route_set.rb
Expand Up @@ -191,14 +191,55 @@ def define_url_helper(route, name, kind, options)
selector = url_helper_name(name, kind)
hash_access_method = hash_access_name(name, kind)

@module.module_eval <<-END_EVAL, __FILE__, __LINE__ + 1
remove_possible_method :#{selector}
def #{selector}(*args)
url_for(#{hash_access_method}(*args))
end
END_EVAL
if optimize_helper?(kind, route)
@module.module_eval <<-END_EVAL, __FILE__, __LINE__ + 1
remove_possible_method :#{selector}
def #{selector}(*args)
if args.size == #{route.required_parts.size} && !args.last.is_a?(Hash) && _optimized_routes?
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
end

# If we are generating a path helper and we don't have a *path segment.
# We can optimize the routes generation to a string interpolation if
# it meets the appropriated runtime conditions.
#
# TODO We are enabling this only for path helpers, remove the
# kind == :path and fix the failures to enable it for url as well.
def optimize_helper?(kind, route) #:nodoc:
kind == :path && route.ast.grep(Journey::Nodes::Star).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

attr_accessor :formatter, :set, :named_routes, :default_scope, :router
Expand Down Expand Up @@ -557,6 +598,10 @@ def generate(options, recall = {}, extras = false)
RESERVED_OPTIONS = [:host, :protocol, :port, :subdomain, :domain, :tld_length,
:trailing_slash, :anchor, :params, :only_path, :script_name]

def mounted?
false
end

def _generate_prefix(options = {})
nil
end
Expand All @@ -568,19 +613,17 @@ def url_for(options)

user, password = extract_authentication(options)
path_segments = options.delete(:_path_segments)
script_name = options.delete(:script_name)

path = (script_name.blank? ? _generate_prefix(options) : script_name.chomp('/')).to_s
script_name = options.delete(:script_name).presence || _generate_prefix(options)

path_options = options.except(*RESERVED_OPTIONS)
path_options = yield(path_options) if block_given?

path_addition, params = generate(path_options, path_segments || {})
path << path_addition
path, params = generate(path_options, path_segments || {})
params.merge!(options[:params] || {})

ActionDispatch::Http::URL.url_for(options.merge!({
:path => path,
:script_name => script_name,
:params => params,
:user => user,
:password => password
Expand Down
5 changes: 5 additions & 0 deletions actionpack/lib/action_dispatch/routing/url_for.rb
Expand Up @@ -152,6 +152,11 @@ def url_for(options = nil)

protected

def _optimized_routes?
return @_optimized_routes if defined?(@_optimized_routes)
@_optimized_routes = default_url_options.empty? && !_routes.mounted? && _routes.default_url_options.empty?
end

def _with_routes(routes)
old_routes, @_routes = @_routes, routes
yield
Expand Down

0 comments on commit d7014bc

Please sign in to comment.