aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib/action_dispatch/routing
diff options
context:
space:
mode:
Diffstat (limited to 'actionpack/lib/action_dispatch/routing')
-rw-r--r--actionpack/lib/action_dispatch/routing/inspector.rb6
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb471
-rw-r--r--actionpack/lib/action_dispatch/routing/polymorphic_routes.rb51
-rw-r--r--actionpack/lib/action_dispatch/routing/redirection.rb2
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb269
-rw-r--r--actionpack/lib/action_dispatch/routing/routes_proxy.rb9
-rw-r--r--actionpack/lib/action_dispatch/routing/url_for.rb13
7 files changed, 493 insertions, 328 deletions
diff --git a/actionpack/lib/action_dispatch/routing/inspector.rb b/actionpack/lib/action_dispatch/routing/inspector.rb
index ea3b2f419d..df5ebb6751 100644
--- a/actionpack/lib/action_dispatch/routing/inspector.rb
+++ b/actionpack/lib/action_dispatch/routing/inspector.rb
@@ -48,7 +48,7 @@ module ActionDispatch
def reqs
@reqs ||= begin
reqs = endpoint
- reqs += " #{constraints.to_s}" unless constraints.empty?
+ reqs += " #{constraints}" unless constraints.empty?
reqs
end
end
@@ -114,9 +114,7 @@ module ActionDispatch
def collect_routes(routes)
routes.collect do |route|
RouteWrapper.new(route)
- end.reject do |route|
- route.internal?
- end.collect do |route|
+ end.reject(&:internal?).collect do |route|
collect_engine_routes(route)
{ name: route.name,
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index 3bc578b379..34b5b48f3a 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -12,9 +12,6 @@ module ActionDispatch
module Routing
class Mapper
URL_OPTIONS = [:protocol, :subdomain, :domain, :host, :port]
- SCOPE_OPTIONS = [:path, :shallow_path, :as, :shallow_prefix, :module,
- :controller, :action, :path_names, :constraints,
- :shallow, :blocks, :defaults, :options]
class Constraints < Endpoint #:nodoc:
attr_reader :app, :constraints
@@ -60,32 +57,74 @@ module ActionDispatch
end
class Mapping #:nodoc:
- IGNORE_OPTIONS = [:to, :as, :via, :on, :constraints, :defaults, :only, :except, :anchor, :shallow, :shallow_path, :shallow_prefix, :format]
ANCHOR_CHARACTERS_REGEX = %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z}
- attr_reader :scope, :options, :requirements, :conditions, :defaults
- attr_reader :to, :default_controller, :default_action
-
- def initialize(scope, path, options)
- @scope = scope
- @requirements, @conditions, @defaults = {}, {}, {}
+ attr_reader :requirements, :conditions, :defaults
+ attr_reader :to, :default_controller, :default_action, :as, :anchor
+ def self.build(scope, set, path, as, options)
options = scope[:options].merge(options) if scope[:options]
- @to = options[:to]
- @default_controller = options[:controller] || scope[:controller]
- @default_action = options[:action] || scope[:action]
- path = normalize_path! path, options[:format]
+ options.delete :only
+ options.delete :except
+ options.delete :shallow_path
+ options.delete :shallow_prefix
+ options.delete :shallow
+
+ defaults = (scope[:defaults] || {}).merge options.delete(:defaults) || {}
+
+ new scope, set, path, defaults, as, options
+ end
+
+ def initialize(scope, set, path, defaults, as, options)
+ @requirements, @conditions = {}, {}
+ @defaults = defaults
+ @set = set
+
+ @to = options.delete :to
+ @default_controller = options.delete(:controller) || scope[:controller]
+ @default_action = options.delete(:action) || scope[:action]
+ @as = as
+ @anchor = options.delete :anchor
+
+ formatted = options.delete :format
+ via = Array(options.delete(:via) { [] })
+ options_constraints = options.delete :constraints
+
+ path = normalize_path! path, formatted
ast = path_ast path
path_params = path_params ast
- @options = normalize_options!(options, path_params, ast)
- normalize_requirements!(path_params)
- normalize_conditions!(path_params, path, ast)
- normalize_defaults!
+
+ options = normalize_options!(options, formatted, path_params, ast, scope[:module])
+
+
+ split_constraints(path_params, scope[:constraints]) if scope[:constraints]
+ constraints = constraints(options, path_params)
+
+ split_constraints path_params, constraints
+
+ @blocks = blocks(options_constraints, scope[:blocks])
+
+ if options_constraints.is_a?(Hash)
+ split_constraints path_params, options_constraints
+ options_constraints.each do |key, default|
+ if URL_OPTIONS.include?(key) && (String === default || Fixnum === default)
+ @defaults[key] ||= default
+ end
+ end
+ end
+
+ normalize_format!(formatted)
+
+ @conditions[:path_info] = path
+ @conditions[:parsed_path_info] = ast
+
+ add_request_method(via, @conditions)
+ normalize_defaults!(options)
end
def to_route
- [ app, conditions, requirements, defaults, options[:as], options[:anchor] ]
+ [ app(@blocks), conditions, requirements, defaults, as, anchor ]
end
private
@@ -106,17 +145,17 @@ module ActionDispatch
format != false && !path.include?(':format') && !path.end_with?('/')
end
- def normalize_options!(options, path_params, path_ast)
+ def normalize_options!(options, formatted, path_params, path_ast, modyoule)
# Add a constraint for wildcard route to make it non-greedy and match the
# optional format part of the route by default
- if options[:format] != false
+ if formatted != false
path_ast.grep(Journey::Nodes::Star) do |node|
options[node.name.to_sym] ||= /.+?/
end
end
if path_params.include?(:controller)
- raise ArgumentError, ":controller segment is not allowed within a namespace block" if scope[:module]
+ raise ArgumentError, ":controller segment is not allowed within a namespace block" if modyoule
# Add a default constraint for :controller path segments that matches namespaced
# controllers with default routes like :controller/:action/:id(.:format), e.g:
@@ -128,23 +167,36 @@ module ActionDispatch
if to.respond_to? :call
options
else
- options.merge!(default_controller_and_action(path_params))
+ to_endpoint = split_to to
+ controller = to_endpoint[0] || default_controller
+ action = to_endpoint[1] || default_action
+
+ controller = add_controller_module(controller, modyoule)
+
+ options.merge! check_controller_and_action(path_params, controller, action)
end
end
- def normalize_requirements!(path_params)
- constraints.each do |key, requirement|
- next unless path_params.include?(key) || key == :controller
- verify_regexp_requirement(requirement) if requirement.is_a?(Regexp)
- @requirements[key] = requirement
+ def split_constraints(path_params, constraints)
+ constraints.each_pair do |key, requirement|
+ if path_params.include?(key) || key == :controller
+ verify_regexp_requirement(requirement) if requirement.is_a?(Regexp)
+ @requirements[key] = requirement
+ else
+ @conditions[key] = requirement
+ end
end
+ end
- if options[:format] == true
+ def normalize_format!(formatted)
+ if formatted == true
@requirements[:format] ||= /.+/
- elsif Regexp === options[:format]
- @requirements[:format] = options[:format]
- elsif String === options[:format]
- @requirements[:format] = Regexp.compile(options[:format])
+ elsif Regexp === formatted
+ @requirements[:format] = formatted
+ @defaults[:format] = nil
+ elsif String === formatted
+ @requirements[:format] = Regexp.compile(formatted)
+ @defaults[:format] = formatted
end
end
@@ -158,31 +210,12 @@ module ActionDispatch
end
end
- def normalize_defaults!
- @defaults.merge!(scope[:defaults]) if scope[:defaults]
- @defaults.merge!(options[:defaults]) if options[:defaults]
-
- options.each do |key, default|
- unless Regexp === default || IGNORE_OPTIONS.include?(key)
+ def normalize_defaults!(options)
+ options.each_pair do |key, default|
+ unless Regexp === default
@defaults[key] = default
end
end
-
- if options[:constraints].is_a?(Hash)
- options[:constraints].each do |key, default|
- if URL_OPTIONS.include?(key) && (String === default || Fixnum === default)
- @defaults[key] ||= default
- end
- end
- elsif options[:constraints]
- verify_callable_constraint(options[:constraints])
- end
-
- if Regexp === options[:format]
- @defaults[:format] = nil
- elsif String === options[:format]
- @defaults[:format] = options[:format]
- end
end
def verify_callable_constraint(callable_constraint)
@@ -191,61 +224,32 @@ module ActionDispatch
end
end
- def normalize_conditions!(path_params, path, ast)
- @conditions[:path_info] = path
- @conditions[:parsed_path_info] = ast
+ def add_request_method(via, conditions)
+ return if via == [:all]
- constraints.each do |key, condition|
- unless path_params.include?(key) || key == :controller
- @conditions[key] = condition
- end
- end
-
- required_defaults = []
- options.each do |key, required_default|
- unless path_params.include?(key) || IGNORE_OPTIONS.include?(key) || Regexp === required_default
- required_defaults << key
- end
- end
- @conditions[:required_defaults] = required_defaults
-
- via_all = options.delete(:via) if options[:via] == :all
-
- if !via_all && options[:via].blank?
+ if via.empty?
msg = "You should not use the `match` method in your router without specifying an HTTP method.\n" \
"If you want to expose your action to both GET and POST, add `via: [:get, :post]` option.\n" \
"If you want to expose your action to GET, use `get` in the router:\n" \
" Instead of: match \"controller#action\"\n" \
" Do: get \"controller#action\""
- raise msg
+ raise ArgumentError, msg
end
- if via = options[:via]
- @conditions[:request_method] = Array(via).map { |m| m.to_s.dasherize.upcase }
- end
+ conditions[:request_method] = via.map { |m| m.to_s.dasherize.upcase }
end
- def app
- return to if Redirect === to
-
+ def app(blocks)
if to.respond_to?(:call)
Constraints.new(to, blocks, false)
+ elsif blocks.any?
+ Constraints.new(dispatcher(defaults), blocks, true)
else
- if blocks.any?
- Constraints.new(dispatcher, blocks, true)
- else
- dispatcher
- end
+ dispatcher(defaults)
end
end
- def default_controller_and_action(path_params)
- controller, action = get_controller_and_action(default_controller,
- default_action,
- to,
- @scope[:module]
- )
-
+ def check_controller_and_action(path_params, controller, action)
hash = check_part(:controller, controller, path_params, {}) do |part|
translate_controller(part) {
message = "'#{part}' is not a supported controller name. This can lead to potential routing problems."
@@ -272,21 +276,24 @@ module ActionDispatch
hash
end
- def get_controller_and_action(controller, action, to, modyoule)
- case to
- when Symbol then action = to.to_s
- when /#/ then controller, action = to.split('#')
- when String then controller = to
+ def split_to(to)
+ if to =~ /#/
+ to.split('#')
+ else
+ []
end
+ end
+ def add_controller_module(controller, modyoule)
if modyoule && !controller.is_a?(Regexp)
if controller =~ %r{\A/}
- controller = controller[1..-1]
+ controller[1..-1]
else
- controller = [modyoule, controller].compact.join("/")
+ [modyoule, controller].compact.join("/")
end
+ else
+ controller
end
- [controller, action]
end
def translate_controller(controller)
@@ -296,24 +303,27 @@ module ActionDispatch
yield
end
- def blocks
- if options[:constraints].present? && !options[:constraints].is_a?(Hash)
- [options[:constraints]]
+ def blocks(options_constraints, scope_blocks)
+ if options_constraints && !options_constraints.is_a?(Hash)
+ verify_callable_constraint(options_constraints)
+ [options_constraints]
else
- scope[:blocks] || []
+ scope_blocks || []
end
end
- def constraints
- @constraints ||= {}.tap do |constraints|
- constraints.merge!(scope[:constraints]) if scope[:constraints]
-
- options.except(*IGNORE_OPTIONS).each do |key, option|
- constraints[key] = option if Regexp === option
+ def constraints(options, path_params)
+ constraints = {}
+ required_defaults = []
+ options.each_pair do |key, option|
+ if Regexp === option
+ constraints[key] = option
+ else
+ required_defaults << key unless path_params.include?(key)
end
-
- constraints.merge!(options[:constraints]) if options[:constraints].is_a?(Hash)
end
+ @conditions[:required_defaults] = required_defaults
+ constraints
end
def path_params(ast)
@@ -325,8 +335,8 @@ module ActionDispatch
parser.parse path
end
- def dispatcher
- Routing::RouteSet::Dispatcher.new(defaults)
+ def dispatcher(defaults)
+ @set.dispatcher defaults
end
end
@@ -363,7 +373,7 @@ module ActionDispatch
# Matches a url pattern to one or more routes.
#
- # You should not use the `match` method in your router
+ # You should not use the +match+ method in your router
# without specifying an HTTP method.
#
# If you want to expose your action to both GET and POST, use:
@@ -374,7 +384,7 @@ module ActionDispatch
# Note that +:controller+, +:action+ and +:id+ are interpreted as url
# query parameters and thus available through +params+ in an action.
#
- # If you want to expose your action to GET, use `get` in the router:
+ # If you want to expose your action to GET, use +get+ in the router:
#
# Instead of:
#
@@ -415,7 +425,7 @@ module ActionDispatch
#
# Because requesting various HTTP verbs with a single action has security
# implications, you must either specify the actions in
- # the via options or use one of the HtttpHelpers[rdoc-ref:HttpHelpers]
+ # the via options or use one of the HttpHelpers[rdoc-ref:HttpHelpers]
# instead +match+
#
# === Options
@@ -429,7 +439,7 @@ module ActionDispatch
# The route's action.
#
# [:param]
- # Overrides the default resource identifier `:id` (name of the
+ # Overrides the default resource identifier +:id+ (name of the
# dynamic segment used to generate the routes).
# You can access that segment from your controller using
# <tt>params[<:param>]</tt>.
@@ -553,13 +563,15 @@ module ActionDispatch
raise "A rack application must be specified" unless path
- options[:as] ||= app_name(app)
+ rails_app = rails_app? app
+ options[:as] ||= app_name(app, rails_app)
+
target_as = name_for_action(options[:as], path)
options[:via] ||= :all
match(path, options.merge(:to => app, :anchor => false, :format => false))
- define_generate_prefix(app, target_as)
+ define_generate_prefix(app, target_as) if rails_app
self
end
@@ -580,31 +592,33 @@ module ActionDispatch
end
private
- def app_name(app)
- return unless app.respond_to?(:routes)
+ def rails_app?(app)
+ app.is_a?(Class) && app < Rails::Railtie
+ end
- if app.respond_to?(:railtie_name)
+ def app_name(app, rails_app)
+ if rails_app
app.railtie_name
- else
- class_name = app.class.is_a?(Class) ? app.name : app.class.name
+ elsif app.is_a?(Class)
+ class_name = app.name
ActiveSupport::Inflector.underscore(class_name).tr("/", "_")
end
end
def define_generate_prefix(app, name)
- return unless app.respond_to?(:routes) && app.routes.respond_to?(:define_mounted_helper)
-
- _route = @set.named_routes.routes[name.to_sym]
+ _route = @set.named_routes.get name
_routes = @set
app.routes.define_mounted_helper(name)
app.routes.extend Module.new {
- def mounted?; true; end
+ def optimize_routes_generation?; false; end
define_method :find_script_name do |options|
- super(options) || begin
- prefix_options = options.slice(*_route.segment_keys)
- # we must actually delete prefix segment keys to avoid passing them to next url_for
- _route.segment_keys.each { |k| options.delete(k) }
- _routes.url_helpers.send("#{name}_path", prefix_options)
+ if options.key? :script_name
+ super(options)
+ else
+ prefix_options = options.slice(*_route.segment_keys)
+ # we must actually delete prefix segment keys to avoid passing them to next url_for
+ _route.segment_keys.each { |k| options.delete(k) }
+ _routes.url_helpers.send("#{name}_path", prefix_options)
end
end
}
@@ -694,7 +708,7 @@ module ActionDispatch
# resources :posts, module: "admin"
#
# If you want to route /admin/posts to +PostsController+
- # (without the Admin:: module prefix), you could use
+ # (without the <tt>Admin::</tt> module prefix), you could use
#
# scope "/admin" do
# resources :posts
@@ -748,7 +762,7 @@ module ActionDispatch
# end
def scope(*args)
options = args.extract_options!.dup
- recover = {}
+ scope = {}
options[:path] = args.flatten.join('/') if args.any?
options[:constraints] ||= {}
@@ -768,7 +782,7 @@ module ActionDispatch
block, options[:constraints] = options[:constraints], {}
end
- SCOPE_OPTIONS.each do |option|
+ @scope.options.each do |option|
if option == :blocks
value = block
elsif option == :options
@@ -778,15 +792,15 @@ module ActionDispatch
end
if value
- recover[option] = @scope[option]
- @scope[option] = send("merge_#{option}_scope", @scope[option], value)
+ scope[option] = send("merge_#{option}_scope", @scope[option], value)
end
end
+ @scope = @scope.new scope
yield
self
ensure
- @scope.merge!(recover)
+ @scope = @scope.parent
end
# Scopes routes to a specific controller
@@ -1024,8 +1038,6 @@ module ActionDispatch
VALID_ON_OPTIONS = [:new, :collection, :member]
RESOURCE_OPTIONS = [:as, :controller, :path, :only, :except, :param, :concerns]
CANONICAL_ACTIONS = %w(index create new show update destroy)
- RESOURCE_METHOD_SCOPES = [:collection, :member, :new]
- RESOURCE_SCOPES = [:resource, :resources]
class Resource #:nodoc:
attr_reader :controller, :path, :options, :param
@@ -1411,7 +1423,7 @@ module ActionDispatch
end
with_scope_level(:nested) do
- if shallow? && shallow_nesting_depth > 1
+ if shallow? && shallow_nesting_depth >= 1
shallow_scope(parent_resource.nested_scope, nested_options) { yield }
else
scope(parent_resource.nested_scope, nested_options) { yield }
@@ -1445,7 +1457,20 @@ module ActionDispatch
if rest.empty? && Hash === path
options = path
path, to = options.find { |name, _value| name.is_a?(String) }
- options[:to] = to
+
+ case to
+ when Symbol
+ options[:action] = to
+ when String
+ if to =~ /#/
+ options[:to] = to
+ else
+ options[:controller] = to
+ end
+ else
+ options[:to] = to
+ end
+
options.delete(path)
paths = [path]
else
@@ -1479,14 +1504,14 @@ module ActionDispatch
end
def using_match_shorthand?(path, options)
- path && (options[:to] || options[:action]).nil? && path =~ %r{/[\w/]+$}
+ path && (options[:to] || options[:action]).nil? && path =~ %r{^/?[-\w]+/[-\w/]+$}
end
def decomposed_match(path, options) # :nodoc:
if on = options.delete(:on)
send(on) { decomposed_match(path, options) }
else
- case @scope[:scope_level]
+ case @scope.scope_level
when :resources
nested { decomposed_match(path, options) }
when :resource
@@ -1509,13 +1534,13 @@ module ActionDispatch
action = nil
end
- if !options.fetch(:as, true)
- options.delete(:as)
- else
- options[:as] = name_for_action(options[:as], action)
- end
+ as = if !options.fetch(:as, true) # if it's set to nil or false
+ options.delete(:as)
+ else
+ name_for_action(options.delete(:as), action)
+ end
- mapping = Mapping.new(@scope, URI.parser.escape(path), options)
+ mapping = Mapping.build(@scope, @set, URI.parser.escape(path), as, options)
app, conditions, requirements, defaults, as, anchor = mapping.to_route
@set.add_route(app, conditions, requirements, defaults, as, anchor)
end
@@ -1529,7 +1554,7 @@ module ActionDispatch
raise ArgumentError, "must be called with a path and/or options"
end
- if @scope[:scope_level] == :resources
+ if @scope.resources?
with_scope_level(:root) do
scope(parent_resource.path) do
super(options)
@@ -1596,40 +1621,39 @@ module ActionDispatch
end
def resource_scope? #:nodoc:
- RESOURCE_SCOPES.include? @scope[:scope_level]
+ @scope.resource_scope?
end
def resource_method_scope? #:nodoc:
- RESOURCE_METHOD_SCOPES.include? @scope[:scope_level]
+ @scope.resource_method_scope?
end
def nested_scope? #:nodoc:
- @scope[:scope_level] == :nested
+ @scope.nested?
end
def with_exclusive_scope
begin
- old_name_prefix, old_path = @scope[:as], @scope[:path]
- @scope[:as], @scope[:path] = nil, nil
+ @scope = @scope.new(:as => nil, :path => nil)
with_scope_level(:exclusive) do
yield
end
ensure
- @scope[:as], @scope[:path] = old_name_prefix, old_path
+ @scope = @scope.parent
end
end
def with_scope_level(kind)
- old, @scope[:scope_level] = @scope[:scope_level], kind
+ @scope = @scope.new_level(kind)
yield
ensure
- @scope[:scope_level] = old
+ @scope = @scope.parent
end
def resource_scope(kind, resource) #:nodoc:
resource.shallow = @scope[:shallow]
- old_resource, @scope[:scope_level_resource] = @scope[:scope_level_resource], resource
+ @scope = @scope.new(:scope_level_resource => resource)
@nesting.push(resource)
with_scope_level(kind) do
@@ -1637,7 +1661,7 @@ module ActionDispatch
end
ensure
@nesting.pop
- @scope[:scope_level_resource] = old_resource
+ @scope = @scope.parent
end
def nested_options #:nodoc:
@@ -1665,21 +1689,22 @@ module ActionDispatch
@scope[:constraints][parent_resource.param]
end
- def canonical_action?(action, flag) #:nodoc:
- flag && resource_method_scope? && CANONICAL_ACTIONS.include?(action.to_s)
+ def canonical_action?(action) #:nodoc:
+ resource_method_scope? && CANONICAL_ACTIONS.include?(action.to_s)
end
def shallow_scope(path, options = {}) #:nodoc:
- old_name_prefix, old_path = @scope[:as], @scope[:path]
- @scope[:as], @scope[:path] = @scope[:shallow_prefix], @scope[:shallow_path]
+ scope = { :as => @scope[:shallow_prefix],
+ :path => @scope[:shallow_path] }
+ @scope = @scope.new scope
scope(path, options) { yield }
ensure
- @scope[:as], @scope[:path] = old_name_prefix, old_path
+ @scope = @scope.parent
end
def path_for_action(action, path) #:nodoc:
- if canonical_action?(action, path.blank?)
+ if path.blank? && canonical_action?(action)
@scope[:path].to_s
else
"#{@scope[:path]}/#{action_path(action, path)}"
@@ -1694,15 +1719,17 @@ module ActionDispatch
def prefix_name_for_action(as, action) #:nodoc:
if as
prefix = as
- elsif !canonical_action?(action, @scope[:scope_level])
+ elsif !canonical_action?(action)
prefix = action
end
- prefix.to_s.tr('-', '_') if prefix
+
+ if prefix && prefix != '/' && !prefix.empty?
+ Mapper.normalize_name prefix.to_s.tr('-', '_')
+ end
end
def name_for_action(as, action) #:nodoc:
prefix = prefix_name_for_action(as, action)
- prefix = Mapper.normalize_name(prefix) if prefix
name_prefix = @scope[:as]
if parent_resource
@@ -1712,27 +1739,15 @@ module ActionDispatch
member_name = parent_resource.member_name
end
- name = case @scope[:scope_level]
- when :nested
- [name_prefix, prefix]
- when :collection
- [prefix, name_prefix, collection_name]
- when :new
- [prefix, :new, name_prefix, member_name]
- when :member
- [prefix, name_prefix, member_name]
- when :root
- [name_prefix, collection_name, prefix]
- else
- [name_prefix, member_name, prefix]
- end
+ action_name = @scope.action_name(name_prefix, prefix, collection_name, member_name)
+ candidate = action_name.select(&:present?).join('_')
- if candidate = name.select(&:present?).join("_").presence
+ unless candidate.empty?
# If a name was not explicitly given, we check if it is valid
# and return nil in case it isn't. Otherwise, we pass the invalid name
# forward so the underlying router engine treats it and raises an exception.
if as.nil?
- candidate unless @set.routes.find { |r| r.name == candidate } || candidate !~ /\A[_a-z]/i
+ candidate unless candidate !~ /\A[_a-z]/i || @set.named_routes.key?(candidate)
else
candidate
end
@@ -1857,9 +1872,83 @@ module ActionDispatch
end
end
+ class Scope # :nodoc:
+ OPTIONS = [:path, :shallow_path, :as, :shallow_prefix, :module,
+ :controller, :action, :path_names, :constraints,
+ :shallow, :blocks, :defaults, :options]
+
+ RESOURCE_SCOPES = [:resource, :resources]
+ RESOURCE_METHOD_SCOPES = [:collection, :member, :new]
+
+ attr_reader :parent, :scope_level
+
+ def initialize(hash, parent = {}, scope_level = nil)
+ @hash = hash
+ @parent = parent
+ @scope_level = scope_level
+ end
+
+ def nested?
+ scope_level == :nested
+ end
+
+ def resources?
+ scope_level == :resources
+ end
+
+ def resource_method_scope?
+ RESOURCE_METHOD_SCOPES.include? scope_level
+ end
+
+ def action_name(name_prefix, prefix, collection_name, member_name)
+ case scope_level
+ when :nested
+ [name_prefix, prefix]
+ when :collection
+ [prefix, name_prefix, collection_name]
+ when :new
+ [prefix, :new, name_prefix, member_name]
+ when :member
+ [prefix, name_prefix, member_name]
+ when :root
+ [name_prefix, collection_name, prefix]
+ else
+ [name_prefix, member_name, prefix]
+ end
+ end
+
+ def resource_scope?
+ RESOURCE_SCOPES.include? scope_level
+ end
+
+ def options
+ OPTIONS
+ end
+
+ def new(hash)
+ self.class.new hash, self, scope_level
+ end
+
+ def new_level(level)
+ self.class.new(self, self, level)
+ end
+
+ def fetch(key, &block)
+ @hash.fetch(key, &block)
+ end
+
+ def [](key)
+ @hash.fetch(key) { @parent[key] }
+ end
+
+ def []=(k,v)
+ @hash[k] = v
+ end
+ end
+
def initialize(set) #:nodoc:
@set = set
- @scope = { :path_names => @set.resources_path_names }
+ @scope = Scope.new({ :path_names => @set.resources_path_names })
@concerns = {}
@nesting = []
end
diff --git a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
index bd3696cda1..2e116ea9cd 100644
--- a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
+++ b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
@@ -1,5 +1,3 @@
-require 'action_controller/model_naming'
-
module ActionDispatch
module Routing
# Polymorphic URL helpers are methods for smart resolution to a named route call when
@@ -55,8 +53,6 @@ module ActionDispatch
# form_for([blog, @post]) # => "/blog/posts/1"
#
module PolymorphicRoutes
- include ActionController::ModelNaming
-
# Constructs a call to a named RESTful route for the given record and returns the
# resulting URL string. For example:
#
@@ -116,7 +112,6 @@ module ActionDispatch
action,
type,
opts
-
end
# Returns the path component of a URL for the given record. It uses
@@ -142,22 +137,26 @@ module ActionDispatch
%w(edit new).each do |action|
module_eval <<-EOT, __FILE__, __LINE__ + 1
- def #{action}_polymorphic_url(record_or_hash, options = {}) # def edit_polymorphic_url(record_or_hash, options = {})
- polymorphic_url( # polymorphic_url(
- record_or_hash, # record_or_hash,
- options.merge(:action => "#{action}")) # options.merge(:action => "edit"))
- end # end
- #
- def #{action}_polymorphic_path(record_or_hash, options = {}) # def edit_polymorphic_path(record_or_hash, options = {})
- polymorphic_url( # polymorphic_url(
- record_or_hash, # record_or_hash,
- options.merge(:action => "#{action}", :routing_type => :path)) # options.merge(:action => "edit", :routing_type => :path))
- end # end
+ def #{action}_polymorphic_url(record_or_hash, options = {})
+ polymorphic_url_for_action("#{action}", record_or_hash, options)
+ end
+
+ def #{action}_polymorphic_path(record_or_hash, options = {})
+ polymorphic_path_for_action("#{action}", record_or_hash, options)
+ end
EOT
end
private
+ def polymorphic_url_for_action(action, record_or_hash, options)
+ polymorphic_url(record_or_hash, options.merge(:action => action))
+ end
+
+ def polymorphic_path_for_action(action, record_or_hash, options)
+ polymorphic_path(record_or_hash, options.merge(:action => action))
+ end
+
class HelperMethodBuilder # :nodoc:
CACHE = { 'path' => {}, 'url' => {} }
@@ -192,7 +191,8 @@ module ActionDispatch
case record_or_hash_or_array
when Array
- if record_or_hash_or_array.empty? || record_or_hash_or_array.include?(nil)
+ record_or_hash_or_array = record_or_hash_or_array.compact
+ if record_or_hash_or_array.empty?
raise ArgumentError, "Nil location provided. Can't build URI."
end
if record_or_hash_or_array.first.is_a?(ActionDispatch::Routing::RoutesProxy)
@@ -247,11 +247,11 @@ module ActionDispatch
args = []
model = record.to_model
- name = if record.persisted?
+ name = if model.persisted?
args << model
- model.class.model_name.singular_route_key
+ model.model_name.singular_route_key
else
- @key_strategy.call model.class.model_name
+ @key_strategy.call model.model_name
end
named_route = prefix + "#{name}_#{suffix}"
@@ -279,7 +279,7 @@ module ActionDispatch
parent.model_name.singular_route_key
else
args << parent.to_model
- parent.to_model.class.model_name.singular_route_key
+ parent.to_model.model_name.singular_route_key
end
}
@@ -290,11 +290,12 @@ module ActionDispatch
when Class
@key_strategy.call record.model_name
else
- if record.persisted?
- args << record.to_model
- record.to_model.class.model_name.singular_route_key
+ model = record.to_model
+ if model.persisted?
+ args << model
+ model.model_name.singular_route_key
else
- @key_strategy.call record.to_model.class.model_name
+ @key_strategy.call model.model_name
end
end
diff --git a/actionpack/lib/action_dispatch/routing/redirection.rb b/actionpack/lib/action_dispatch/routing/redirection.rb
index b1b39a5496..3c1c4fadf6 100644
--- a/actionpack/lib/action_dispatch/routing/redirection.rb
+++ b/actionpack/lib/action_dispatch/routing/redirection.rb
@@ -37,7 +37,7 @@ module ActionDispatch
uri.host ||= req.host
uri.port ||= req.port unless req.standard_port?
- body = %(<html><body>You are being <a href="#{ERB::Util.h(uri.to_s)}">redirected</a>.</body></html>)
+ body = %(<html><body>You are being <a href="#{ERB::Util.unwrapped_html_escape(uri.to_s)}">redirected</a>.</body></html>)
headers = {
'Location' => uri.to_s,
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index bdda802195..ce04f0b08a 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -12,14 +12,15 @@ require 'action_dispatch/routing/endpoint'
module ActionDispatch
module Routing
- class RouteSet #:nodoc:
+ # :stopdoc:
+ class RouteSet
# 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
- class Dispatcher < Routing::Endpoint #:nodoc:
+ class Dispatcher < Routing::Endpoint
def initialize(defaults)
@defaults = defaults
@controller_class_names = ThreadSafe::Cache.new
@@ -84,38 +85,66 @@ module ActionDispatch
# A NamedRouteCollection instance is a collection of named routes, and also
# maintains an anonymous module that can be used to install helpers for the
# named routes.
- class NamedRouteCollection #:nodoc:
+ class NamedRouteCollection
include Enumerable
- attr_reader :routes, :helpers, :module
+ attr_reader :routes, :url_helpers_module, :path_helpers_module
def initialize
@routes = {}
- @helpers = []
- @module = Module.new
+ @path_helpers = Set.new
+ @url_helpers = Set.new
+ @url_helpers_module = Module.new
+ @path_helpers_module = Module.new
+ end
+
+ def route_defined?(name)
+ key = name.to_sym
+ @path_helpers.include?(key) || @url_helpers.include?(key)
end
def helper_names
- @helpers.map(&:to_s)
+ @path_helpers.map(&:to_s) + @url_helpers.map(&:to_s)
end
def clear!
- @helpers.each do |helper|
- @module.remove_possible_method helper
+ @path_helpers.each do |helper|
+ @path_helpers_module.send :undef_method, helper
+ end
+
+ @url_helpers.each do |helper|
+ @url_helpers_module.send :undef_method, helper
end
@routes.clear
- @helpers.clear
+ @path_helpers.clear
+ @url_helpers.clear
end
def add(name, route)
- routes[name.to_sym] = route
- define_named_route_methods(name, route)
+ key = name.to_sym
+ path_name = :"#{name}_path"
+ url_name = :"#{name}_url"
+
+ if routes.key? key
+ @path_helpers_module.send :undef_method, path_name
+ @url_helpers_module.send :undef_method, url_name
+ end
+ routes[key] = route
+ define_url_helper @path_helpers_module, route, path_name, route.defaults, name, PATH
+ define_url_helper @url_helpers_module, route, url_name, route.defaults, name, UNKNOWN
+
+ @path_helpers << path_name
+ @url_helpers << url_name
end
def get(name)
routes[name.to_sym]
end
+ def key?(name)
+ routes.key? name.to_sym
+ end
+
alias []= add
alias [] get
alias clear clear!
@@ -133,12 +162,12 @@ module ActionDispatch
routes.length
end
- class UrlHelper # :nodoc:
- def self.create(route, options)
+ class UrlHelper
+ def self.create(route, options, route_name, url_strategy)
if optimize_helper?(route)
- OptimizedUrlHelper.new(route, options)
+ OptimizedUrlHelper.new(route, options, route_name, url_strategy)
else
- new route, options
+ new route, options, route_name, url_strategy
end
end
@@ -146,20 +175,22 @@ module ActionDispatch
!route.glob? && route.path.requirements.empty?
end
- class OptimizedUrlHelper < UrlHelper # :nodoc:
+ attr_reader :url_strategy, :route_name
+
+ class OptimizedUrlHelper < UrlHelper
attr_reader :arg_size
- def initialize(route, options)
+ def initialize(route, options, route_name, url_strategy)
super
@required_parts = @route.required_parts
@arg_size = @required_parts.size
end
- def call(t, args)
- if args.size == arg_size && !args.last.is_a?(Hash) && optimize_routes_generation?(t)
+ def call(t, args, inner_options)
+ if args.size == arg_size && !inner_options && optimize_routes_generation?(t)
options = t.url_options.merge @options
options[:path] = optimized_helper(args)
- ActionDispatch::Http::URL.url_for(options)
+ url_strategy.call options
else
super
end
@@ -201,30 +232,43 @@ module ActionDispatch
end
end
- def initialize(route, options)
+ def initialize(route, options, route_name, url_strategy)
@options = options
@segment_keys = route.segment_keys.uniq
@route = route
+ @url_strategy = url_strategy
+ @route_name = route_name
end
- def call(t, args)
+ def call(t, args, inner_options)
controller_options = t.url_options
options = controller_options.merge @options
- hash = handle_positional_args(controller_options, args, options, @segment_keys)
- t._routes.url_for(hash)
- end
+ hash = handle_positional_args(controller_options,
+ inner_options || {},
+ args,
+ options,
+ @segment_keys)
- def handle_positional_args(controller_options, args, result, path_params)
- inner_options = args.extract_options!
+ t._routes.url_for(hash, route_name, url_strategy)
+ end
+ def handle_positional_args(controller_options, inner_options, args, result, path_params)
if args.size > 0
- if args.size < path_params.size - 1 # take format into account
+ # take format into account
+ if path_params.include?(:format)
+ path_params_size = path_params.size - 1
+ else
+ path_params_size = path_params.size
+ end
+
+ if args.size < path_params_size
path_params -= controller_options.keys
path_params -= result.keys
end
- path_params.each { |param|
- result[param] = inner_options[param] || args.shift
- }
+ path_params -= inner_options.keys
+ path_params.take(args.size).each do |param|
+ result[param] = args.shift
+ end
end
result.merge!(inner_options)
@@ -245,27 +289,22 @@ module ActionDispatch
#
# foo_url(bar, baz, bang, sort_by: 'baz')
#
- def define_url_helper(route, name, options)
- helper = UrlHelper.create(route, options.dup)
-
- @module.remove_possible_method name
- @module.module_eval do
+ def define_url_helper(mod, route, name, opts, route_key, url_strategy)
+ helper = UrlHelper.create(route, opts, route_key, url_strategy)
+ mod.module_eval do
define_method(name) do |*args|
- helper.call self, args
+ options = nil
+ options = args.pop if args.last.is_a? Hash
+ helper.call self, args, options
end
end
-
- helpers << name
- end
-
- def define_named_route_methods(name, route)
- define_url_helper route, :"#{name}_path",
- route.defaults.merge(:use_route => name, :only_path => true)
- define_url_helper route, :"#{name}_url",
- route.defaults.merge(:use_route => name, :only_path => false)
end
end
+ # strategy for building urls to send to the client
+ PATH = ->(options) { ActionDispatch::Http::URL.path_for(options) }
+ UNKNOWN = ->(options) { ActionDispatch::Http::URL.url_for(options) }
+
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
@@ -278,7 +317,7 @@ module ActionDispatch
def initialize(request_class = ActionDispatch::Request)
self.named_routes = NamedRouteCollection.new
- self.resources_path_names = self.class.default_resources_path_names.dup
+ self.resources_path_names = self.class.default_resources_path_names
self.default_url_options = {}
self.request_class = request_class
@@ -319,6 +358,7 @@ module ActionDispatch
mapper.instance_exec(&block)
end
end
+ private :eval_block
def finalize!
return if @finalized
@@ -334,7 +374,11 @@ module ActionDispatch
@prepend.each { |blk| eval_block(blk) }
end
- module MountedHelpers #:nodoc:
+ def dispatcher(defaults)
+ Routing::RouteSet::Dispatcher.new(defaults)
+ end
+
+ module MountedHelpers
extend ActiveSupport::Concern
include UrlFor
end
@@ -351,9 +395,11 @@ module ActionDispatch
return if MountedHelpers.method_defined?(name)
routes = self
+ helpers = routes.url_helpers
+
MountedHelpers.class_eval do
define_method "_#{name}" do
- RoutesProxy.new(routes, _routes_context)
+ RoutesProxy.new(routes, _routes_context, helpers)
end
end
@@ -364,42 +410,62 @@ module ActionDispatch
RUBY
end
- def url_helpers
- @url_helpers ||= begin
- routes = self
-
- 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, :optimize_routes_generation?, :to => '@_routes'
- attr_reader :_routes
- def url_options; {}; end
- end
+ def url_helpers(supports_path = true)
+ routes = self
- # 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
+ Module.new do
+ extend ActiveSupport::Concern
+ include UrlFor
- # Any class that includes this module will get all
- # named routes...
- include routes.named_routes.module
+ # Define url_for in the singleton level so one can do:
+ # Rails.application.routes.url_helpers.url_for(args)
+ @_routes = routes
+ class << self
+ def url_for(options)
+ @_routes.url_for(options)
+ end
- # plus a singleton class method called _routes ...
- included do
- singleton_class.send(:redefine_method, :_routes) { routes }
+ def optimize_routes_generation?
+ @_routes.optimize_routes_generation?
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 }
+ attr_reader :_routes
+ def url_options; {}; end
+ end
+
+ url_helpers = routes.named_routes.url_helpers_module
+
+ # Make named_routes available in the module singleton
+ # as well, so one can do:
+ # Rails.application.routes.url_helpers.posts_path
+ extend url_helpers
+
+ # Any class that includes this module will get all
+ # named routes...
+ include url_helpers
+
+ if supports_path
+ path_helpers = routes.named_routes.path_helpers_module
+
+ include path_helpers
+ extend path_helpers
end
+
+ # plus a singleton class method called _routes ...
+ included do
+ 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 }
+
+ define_method(:_generate_paths_by_default) do
+ supports_path
+ end
+
+ private :_generate_paths_by_default
end
end
@@ -420,15 +486,15 @@ module ActionDispatch
path = conditions.delete :path_info
ast = conditions.delete :parsed_path_info
- path = build_path(path, ast, requirements, SEPARATORS, anchor)
- conditions = build_conditions(conditions, path.names.map { |x| x.to_sym })
+ path = build_path(path, ast, requirements, anchor)
+ conditions = build_conditions(conditions, path.names.map(&:to_sym))
route = @set.add_route(app, path, conditions, defaults, name)
named_routes[name] = route if name
route
end
- def build_path(path, ast, requirements, separators, anchor)
+ def build_path(path, ast, requirements, anchor)
strexp = Journey::Router::Strexp.new(
ast,
path,
@@ -478,12 +544,12 @@ module ActionDispatch
end
private :build_conditions
- class Generator #:nodoc:
+ class Generator
PARAMETERIZE = lambda do |name, value|
if name == :controller
value
elsif value.is_a?(Array)
- value.map { |v| v.to_param }.join('/')
+ value.map(&:to_param).join('/')
elsif param = value.to_param
param
end
@@ -491,8 +557,8 @@ module ActionDispatch
attr_reader :options, :recall, :set, :named_route
- def initialize(options, recall, set)
- @named_route = options.delete(:use_route)
+ def initialize(named_route, options, recall, set)
+ @named_route = named_route
@options = options.dup
@recall = recall.dup
@set = set
@@ -608,32 +674,34 @@ module ActionDispatch
end
def generate_extras(options, recall={})
- path, params = generate(options, recall)
+ route_key = options.delete :use_route
+ path, params = generate(route_key, options, recall)
return path, params.keys
end
- def generate(options, recall = {})
- Generator.new(options, recall, self).generate
+ def generate(route_key, options, recall = {})
+ Generator.new(route_key, options, recall, self).generate
end
+ private :generate
RESERVED_OPTIONS = [:host, :protocol, :port, :subdomain, :domain, :tld_length,
:trailing_slash, :anchor, :params, :only_path, :script_name,
:original_script_name]
- def mounted?
- false
- end
-
def optimize_routes_generation?
- !mounted? && default_url_options.empty?
+ default_url_options.empty?
end
def find_script_name(options)
- options.delete :script_name
+ options.delete(:script_name) || ''
+ end
+
+ def path_for(options, route_name = nil)
+ url_for(options, route_name, PATH)
end
# The +options+ argument must be a hash whose keys are *symbols*.
- def url_for(options)
+ def url_for(options, route_name = nil, url_strategy = UNKNOWN)
options = default_url_options.merge options
user = password = nil
@@ -648,14 +716,14 @@ module ActionDispatch
original_script_name = options.delete(:original_script_name)
script_name = find_script_name options
- if script_name && original_script_name
+ if original_script_name
script_name = original_script_name + script_name
end
path_options = options.dup
RESERVED_OPTIONS.each { |ro| path_options.delete ro }
- path, params = generate(path_options, recall)
+ path, params = generate(route_name, path_options, recall)
if options.key? :params
params.merge! options[:params]
@@ -667,7 +735,7 @@ module ActionDispatch
options[:user] = user
options[:password] = password
- ActionDispatch::Http::URL.url_for(options)
+ url_strategy.call options
end
def call(env)
@@ -714,5 +782,6 @@ module ActionDispatch
raise ActionController::RoutingError, "No route matches #{path.inspect}"
end
end
+ # :startdoc:
end
end
diff --git a/actionpack/lib/action_dispatch/routing/routes_proxy.rb b/actionpack/lib/action_dispatch/routing/routes_proxy.rb
index e2393d3799..040ea04046 100644
--- a/actionpack/lib/action_dispatch/routing/routes_proxy.rb
+++ b/actionpack/lib/action_dispatch/routing/routes_proxy.rb
@@ -8,8 +8,9 @@ module ActionDispatch
attr_accessor :scope, :routes
alias :_routes :routes
- def initialize(routes, scope)
+ def initialize(routes, scope, helpers)
@routes, @scope = routes, scope
+ @helpers = helpers
end
def url_options
@@ -19,16 +20,16 @@ module ActionDispatch
end
def respond_to?(method, include_private = false)
- super || routes.url_helpers.respond_to?(method)
+ super || @helpers.respond_to?(method)
end
def method_missing(method, *args)
- if routes.url_helpers.respond_to?(method)
+ if @helpers.respond_to?(method)
self.class.class_eval <<-RUBY, __FILE__, __LINE__ + 1
def #{method}(*args)
options = args.extract_options!
args << url_options.merge((options || {}).symbolize_keys)
- routes.url_helpers.#{method}(*args)
+ @helpers.#{method}(*args)
end
RUBY
send(method, *args)
diff --git a/actionpack/lib/action_dispatch/routing/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb
index e624fe3c4a..dca86858cc 100644
--- a/actionpack/lib/action_dispatch/routing/url_for.rb
+++ b/actionpack/lib/action_dispatch/routing/url_for.rb
@@ -152,7 +152,9 @@ module ActionDispatch
when nil
_routes.url_for(url_options.symbolize_keys)
when Hash
- _routes.url_for(options.symbolize_keys.reverse_merge!(url_options))
+ route_name = options.delete :use_route
+ _routes.url_for(options.symbolize_keys.reverse_merge!(url_options),
+ route_name)
when String
options
when Symbol
@@ -169,8 +171,7 @@ module ActionDispatch
protected
def optimize_routes_generation?
- return @_optimized_routes if defined?(@_optimized_routes)
- @_optimized_routes = _routes.optimize_routes_generation? && default_url_options.empty?
+ _routes.optimize_routes_generation? && default_url_options.empty?
end
def _with_routes(routes)
@@ -183,6 +184,12 @@ module ActionDispatch
def _routes_context
self
end
+
+ private
+
+ def _generate_paths_by_default
+ true
+ end
end
end
end