aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib/action_dispatch/routing/route_set.rb
diff options
context:
space:
mode:
Diffstat (limited to 'actionpack/lib/action_dispatch/routing/route_set.rb')
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb272
1 files changed, 172 insertions, 100 deletions
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index 2bcde16110..7a7810a95c 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -9,6 +9,12 @@ require 'action_controller/metal/exceptions'
module ActionDispatch
module Routing
class RouteSet #:nodoc:
+ # Since the router holds references to many parts of the system
+ # like engines, controllers and the application itself, inspecting
+ # the route set can actually be really slow, therefore we default
+ # alias inspect to to_s.
+ alias inspect to_s
+
PARAMETERS_KEY = 'action_dispatch.request.path_parameters'
class Dispatcher #:nodoc:
@@ -31,6 +37,7 @@ module ActionDispatch
end
def prepare_params!(params)
+ normalize_controller!(params)
merge_default_action!(params)
split_glob_param!(params) if @glob_param
end
@@ -66,6 +73,10 @@ module ActionDispatch
controller.action(action).call(env)
end
+ def normalize_controller!(params)
+ params[:controller] = params[:controller].underscore if params.key?(:controller)
+ end
+
def merge_default_action!(params)
params[:action] ||= 'index'
end
@@ -83,7 +94,27 @@ module ActionDispatch
attr_reader :routes, :helpers, :module
def initialize
- clear!
+ @routes = {}
+ @helpers = []
+ @module = Module.new do
+ protected
+
+ def handle_positional_args(args, options, route)
+ inner_options = args.extract_options!
+ result = options.dup
+
+ if args.any?
+ keys = route.segment_keys
+ if args.size < keys.size - 1 # take format into account
+ keys -= self.url_options.keys if self.respond_to?(:url_options)
+ keys -= options.keys
+ end
+ result.merge!(Hash[keys.zip(args)])
+ end
+
+ result.merge!(inner_options)
+ end
+ end
end
def helper_names
@@ -91,12 +122,8 @@ module ActionDispatch
end
def clear!
- @routes = {}
- @helpers = []
-
- @module ||= Module.new do
- instance_methods.each { |selector| remove_method(selector) }
- end
+ @routes.clear
+ @helpers.clear
end
def add(name, route)
@@ -125,57 +152,40 @@ module ActionDispatch
routes.length
end
- def reset!
- old_routes = routes.dup
- clear!
- old_routes.each do |name, route|
- add(name, route)
- end
- end
-
- def install(destinations = [ActionController::Base, ActionView::Base], regenerate = false)
- reset! if regenerate
- Array(destinations).each do |dest|
- dest.__send__(:include, @module)
- end
- end
-
private
- def url_helper_name(name, kind = :url)
- :"#{name}_#{kind}"
+ def url_helper_name(name, only_path)
+ if only_path
+ :"#{name}_path"
+ else
+ :"#{name}_url"
+ end
end
- def hash_access_name(name, kind = :url)
- :"hash_for_#{name}_#{kind}"
+ def hash_access_name(name, only_path)
+ if only_path
+ :"hash_for_#{name}_path"
+ else
+ :"hash_for_#{name}_url"
+ end
end
def define_named_route_methods(name, route)
- {:url => {:only_path => false}, :path => {:only_path => true}}.each do |kind, opts|
- hash = route.defaults.merge(:use_route => name).merge(opts)
- define_hash_access route, name, kind, hash
- define_url_helper route, name, kind, hash
+ [true, false].each do |only_path|
+ hash = route.defaults.merge(:use_route => name, :only_path => only_path)
+ define_hash_access route, name, hash
+ define_url_helper route, name, hash
end
end
- def define_hash_access(route, name, kind, options)
- selector = hash_access_name(name, kind)
-
- # We use module_eval to avoid leaks
- @module.module_eval <<-END_EVAL, __FILE__, __LINE__ + 1
- remove_possible_method :#{selector}
- def #{selector}(*args)
- options = args.extract_options!
- result = #{options.inspect}
-
- if args.any?
- result[:_positional_args] = args
- result[:_positional_keys] = #{route.segment_keys.inspect}
- end
+ def define_hash_access(route, name, options)
+ selector = hash_access_name(name, options[:only_path])
- result.merge(options)
+ @module.module_eval do
+ redefine_method(selector) do |*args|
+ self.handle_positional_args(args, options, route)
end
- protected :#{selector}
- END_EVAL
+ protected selector
+ end
helpers << selector
end
@@ -192,23 +202,60 @@ module ActionDispatch
#
# foo_url(bar, baz, bang, :sort_by => 'baz')
#
- 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
+ def define_url_helper(route, name, options)
+ selector = url_helper_name(name, options[:only_path])
+ hash_access_method = hash_access_name(name, options[:only_path])
+
+ if optimize_helper?(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) && optimize_routes_generation?
+ 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
+
+ # Clause check about when we need to generate an optimized helper.
+ def optimize_helper?(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
attr_accessor :formatter, :set, :named_routes, :default_scope, :router
attr_accessor :disable_clear_and_finalize, :resources_path_names
attr_accessor :default_url_options, :request_class, :valid_conditions
+ attr_accessor :draw_paths
alias :routes :set
@@ -220,6 +267,7 @@ module ActionDispatch
self.named_routes = NamedRouteCollection.new
self.resources_path_names = self.class.default_resources_path_names.dup
self.default_url_options = {}
+ self.draw_paths = []
self.request_class = request_class
@valid_conditions = {}
@@ -262,8 +310,7 @@ module ActionDispatch
def eval_block(block)
if block.arity == 1
raise "You are using the old router DSL which has been removed in Rails 3.1. " <<
- "Please check how to update your routes file at: http://www.engineyard.com/blog/2010/the-lowdown-on-routes-in-rails-3/ " <<
- "or add the rails_legacy_mapper gem to your Gemfile"
+ "Please check how to update your routes file at: http://www.engineyard.com/blog/2010/the-lowdown-on-routes-in-rails-3/"
end
mapper = Mapper.new(self)
if default_scope
@@ -287,14 +334,15 @@ module ActionDispatch
@prepend.each { |blk| eval_block(blk) }
end
- def install_helpers(destinations = [ActionController::Base, ActionView::Base], regenerate_code = false)
- Array(destinations).each { |d| d.module_eval { include Helpers } }
- named_routes.install(destinations, regenerate_code)
- end
-
- module MountedHelpers
+ module MountedHelpers #:nodoc:
+ extend ActiveSupport::Concern
+ include UrlFor
end
+ # Contains all the mounted helpers accross different
+ # engines and the `main_app` helper for the application.
+ # You can include this in your classes if you want to
+ # access routes for other engines.
def mounted_helpers
MountedHelpers
end
@@ -305,7 +353,7 @@ module ActionDispatch
routes = self
MountedHelpers.class_eval do
define_method "_#{name}" do
- RoutesProxy.new(routes, self._routes_context)
+ RoutesProxy.new(routes, _routes_context)
end
end
@@ -320,28 +368,36 @@ module ActionDispatch
@url_helpers ||= begin
routes = self
- helpers = Module.new do
+ Module.new do
extend ActiveSupport::Concern
include UrlFor
+ # Define url_for in the singleton level so one can do:
+ # Rails.application.routes.url_helpers.url_for(args)
@_routes = routes
class << self
- delegate :url_for, :to => '@_routes'
+ delegate :url_for, :optimize_routes_generation?, :to => '@_routes'
end
+
+ # Make named_routes available in the module singleton
+ # as well, so one can do:
+ # Rails.application.routes.url_helpers.posts_path
extend routes.named_routes.module
- # ROUTES TODO: install_helpers isn't great... can we make a module with the stuff that
- # we can include?
- # Yes plz - JP
+ # Any class that includes this module will get all
+ # named routes...
+ include routes.named_routes.module
+
+ # plus a singleton class method called _routes ...
included do
- routes.install_helpers(self)
singleton_class.send(:redefine_method, :_routes) { routes }
end
+ # And an instance method _routes. Note that
+ # UrlFor (included in this module) add extra
+ # conveniences for working with @_routes.
define_method(:_routes) { @_routes || routes }
end
-
- helpers
end
end
@@ -356,7 +412,7 @@ module ActionDispatch
conditions = build_conditions(conditions, valid_conditions, path.names.map { |x| x.to_sym })
route = @set.add_route(app, path, conditions, defaults, name)
- named_routes[name] = route if name
+ named_routes[name] = route if name && !named_routes[name]
route
end
@@ -367,7 +423,26 @@ module ActionDispatch
SEPARATORS,
anchor)
- Journey::Path::Pattern.new(strexp)
+ pattern = Journey::Path::Pattern.new(strexp)
+
+ builder = Journey::GTG::Builder.new pattern.spec
+
+ # Get all the symbol nodes followed by literals that are not the
+ # dummy node.
+ symbols = pattern.spec.grep(Journey::Nodes::Symbol).find_all { |n|
+ builder.followpos(n).first.literal?
+ }
+
+ # Get all the symbol nodes preceded by literals.
+ symbols.concat pattern.spec.find_all(&:literal?).map { |n|
+ builder.followpos(n).first
+ }.find_all(&:symbol?)
+
+ symbols.each { |x|
+ x.regexp = /(?:#{Regexp.union(x.regexp, '-')})+/
+ }
+
+ pattern
end
private :build_path
@@ -470,7 +545,7 @@ module ActionDispatch
# if the current controller is "foo/bar/baz" and :controller => "baz/bat"
# is specified, the controller becomes "foo/baz/bat"
def use_relative_controller!
- if !named_route && different_controller?
+ if !named_route && different_controller? && !controller.start_with?("/")
old_parts = current_controller.split('/')
size = controller.count("/") + 1
parts = old_parts[0...-size] << controller
@@ -535,30 +610,35 @@ module ActionDispatch
RESERVED_OPTIONS = [:host, :protocol, :port, :subdomain, :domain, :tld_length,
: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 = {})
nil
end
+ # The +options+ argument must be +nil+ or a hash whose keys are *symbols*.
def url_for(options)
- finalize!
- options = (options || {}).reverse_merge!(default_url_options)
-
- handle_positional_args(options)
+ options = default_url_options.merge(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({
+ ActionDispatch::Http::URL.url_for(options.merge!({
:path => path,
+ :script_name => script_name,
:params => params,
:user => user,
:password => password
@@ -566,13 +646,13 @@ module ActionDispatch
end
def call(env)
- finalize!
@router.call(env)
end
def recognize_path(path, environment = {})
method = (environment[:method] || "GET").to_s.upcase
path = Journey::Router::Utils.normalize_path(path) unless path =~ %r{://}
+ extras = environment[:extras] || {}
begin
env = Rack::MockRequest.env_for(path, {:method => method})
@@ -582,13 +662,15 @@ module ActionDispatch
req = @request_class.new(env)
@router.recognize(req) do |route, matches, params|
+ params.merge!(extras)
params.each do |key, value|
if value.is_a?(String)
- value = value.dup.force_encoding(Encoding::BINARY) if value.encoding_aware?
+ value = value.dup.force_encoding(Encoding::BINARY)
params[key] = URI.parser.unescape(value)
end
end
-
+ old_params = env[::ActionDispatch::Routing::RouteSet::PARAMETERS_KEY]
+ env[::ActionDispatch::Routing::RouteSet::PARAMETERS_KEY] = (old_params || {}).merge(params)
dispatcher = route.app
while dispatcher.is_a?(Mapper::Constraints) && dispatcher.matches?(env) do
dispatcher = dispatcher.app
@@ -613,16 +695,6 @@ module ActionDispatch
end
end
- def handle_positional_args(options)
- return unless args = options.delete(:_positional_args)
-
- keys = options.delete(:_positional_keys)
- keys -= options.keys if args.size < keys.size - 1 # take format into account
-
- # Tell url_for to skip default_url_options
- options.merge!(Hash[args.zip(keys).map { |v, k| [k, v] }])
- end
-
end
end
end