mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
When a dynamic :controller segment is present in the path add a Regexp constraint that allow matching on multiple path segments.
Using a namespace block isn't compatible with dynamic routes so we raise an ArgumentError if we detect a :module present in the scope. [#5052 state:resolved] Signed-off-by: José Valim <jose.valim@gmail.com>
This commit is contained in:
parent
f4be0041c6
commit
b802a0d4c7
3 changed files with 46 additions and 21 deletions
|
@ -65,6 +65,16 @@ module ActionDispatch
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if path.match(':controller')
|
||||||
|
raise ArgumentError, ":controller segment is not allowed within a namespace block" if @scope[:module]
|
||||||
|
|
||||||
|
# Add a default constraint for :controller path segments that matches namespaced
|
||||||
|
# controllers with default routes like :controller/:action/:id(.:format), e.g:
|
||||||
|
# GET /admin/products/show/1
|
||||||
|
# => { :controller => 'admin/products', :action => 'show', :id => '1' }
|
||||||
|
options.reverse_merge!(:controller => /.+?/)
|
||||||
|
end
|
||||||
|
|
||||||
path = normalize_path(path)
|
path = normalize_path(path)
|
||||||
path_without_format = path.sub(/\(\.:format\)$/, '')
|
path_without_format = path.sub(/\(\.:format\)$/, '')
|
||||||
|
|
||||||
|
@ -93,7 +103,7 @@ module ActionDispatch
|
||||||
|
|
||||||
def app
|
def app
|
||||||
Constraints.new(
|
Constraints.new(
|
||||||
to.respond_to?(:call) ? to : Routing::RouteSet::Dispatcher.new(:defaults => defaults, :module => @scope[:module]),
|
to.respond_to?(:call) ? to : Routing::RouteSet::Dispatcher.new(:defaults => defaults),
|
||||||
blocks,
|
blocks,
|
||||||
@set.request_class
|
@set.request_class
|
||||||
)
|
)
|
||||||
|
@ -135,8 +145,11 @@ module ActionDispatch
|
||||||
defaults[:controller] ||= default_controller
|
defaults[:controller] ||= default_controller
|
||||||
defaults[:action] ||= default_action
|
defaults[:action] ||= default_action
|
||||||
|
|
||||||
defaults.delete(:controller) if defaults[:controller].blank?
|
defaults.delete(:controller) if defaults[:controller].blank? || defaults[:controller].is_a?(Regexp)
|
||||||
defaults.delete(:action) if defaults[:action].blank?
|
defaults.delete(:action) if defaults[:action].blank? || defaults[:action].is_a?(Regexp)
|
||||||
|
|
||||||
|
defaults[:controller] = defaults[:controller].to_s if defaults.key?(:controller)
|
||||||
|
defaults[:action] = defaults[:action].to_s if defaults.key?(:action)
|
||||||
|
|
||||||
if defaults[:controller].blank? && segment_keys.exclude?("controller")
|
if defaults[:controller].blank? && segment_keys.exclude?("controller")
|
||||||
raise ArgumentError, "missing :controller"
|
raise ArgumentError, "missing :controller"
|
||||||
|
@ -185,15 +198,15 @@ module ActionDispatch
|
||||||
|
|
||||||
def default_controller
|
def default_controller
|
||||||
if @options[:controller]
|
if @options[:controller]
|
||||||
@options[:controller].to_s
|
@options[:controller]
|
||||||
elsif @scope[:controller]
|
elsif @scope[:controller]
|
||||||
@scope[:controller].to_s
|
@scope[:controller]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def default_action
|
def default_action
|
||||||
if @options[:action]
|
if @options[:action]
|
||||||
@options[:action].to_s
|
@options[:action]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -14,7 +14,6 @@ module ActionDispatch
|
||||||
def initialize(options={})
|
def initialize(options={})
|
||||||
@defaults = options[:defaults]
|
@defaults = options[:defaults]
|
||||||
@glob_param = options.delete(:glob)
|
@glob_param = options.delete(:glob)
|
||||||
@module = options.delete(:module)
|
|
||||||
@controllers = {}
|
@controllers = {}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -39,12 +38,11 @@ module ActionDispatch
|
||||||
# we should raise an error in case it's not found, because it usually means
|
# we should raise an error in case it's not found, because it usually means
|
||||||
# an user error. However, if the controller was retrieved through a dynamic
|
# an user error. However, if the controller was retrieved through a dynamic
|
||||||
# segment, as in :controller(/:action), we should simply return nil and
|
# segment, as in :controller(/:action), we should simply return nil and
|
||||||
# delegate the control back to Rack cascade. Besides, if this is not a default
|
# delegate the control back to Rack cascade. Besides, if this is not a default
|
||||||
# controller, it means we should respect the @scope[:module] parameter.
|
# controller, it means we should respect the @scope[:module] parameter.
|
||||||
def controller(params, default_controller=true)
|
def controller(params, default_controller=true)
|
||||||
if params && params.key?(:controller)
|
if params && params.key?(:controller)
|
||||||
controller_param = @module && !default_controller ?
|
controller_param = params[:controller]
|
||||||
"#{@module}/#{params[:controller]}" : params[:controller]
|
|
||||||
controller_reference(controller_param)
|
controller_reference(controller_param)
|
||||||
end
|
end
|
||||||
rescue NameError => e
|
rescue NameError => e
|
||||||
|
|
|
@ -323,7 +323,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
match "whatever/:controller(/:action(/:id))"
|
match "whatever/:controller(/:action(/:id))", :id => /\d+/
|
||||||
|
|
||||||
resource :profile do
|
resource :profile do
|
||||||
get :settings
|
get :settings
|
||||||
|
@ -349,7 +349,6 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
|
||||||
namespace :private do
|
namespace :private do
|
||||||
root :to => redirect('/private/index')
|
root :to => redirect('/private/index')
|
||||||
match "index", :to => 'private#index'
|
match "index", :to => 'private#index'
|
||||||
match ":controller(/:action(/:id))"
|
|
||||||
end
|
end
|
||||||
|
|
||||||
scope :only => [:index, :show] do
|
scope :only => [:index, :show] do
|
||||||
|
@ -527,15 +526,14 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_namespace_with_controller_segment
|
def test_namespace_with_controller_segment
|
||||||
with_test_routes do
|
assert_raise(ArgumentError) do
|
||||||
get '/private/foo'
|
self.class.stub_controllers do |routes|
|
||||||
assert_equal 'private/foo#index', @response.body
|
routes.draw do
|
||||||
|
namespace :admin do
|
||||||
get '/private/foo/bar'
|
match '/:controller(/:action(/:id(.:format)))'
|
||||||
assert_equal 'private/foo#bar', @response.body
|
end
|
||||||
|
end
|
||||||
get '/private/foo/bar/1'
|
end
|
||||||
assert_equal 'private/foo#bar', @response.body
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1354,6 +1352,22 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_url_generator_for_namespaced_generic_route
|
||||||
|
with_test_routes do
|
||||||
|
get 'whatever/foo/bar/show'
|
||||||
|
assert_equal 'foo/bar#show', @response.body
|
||||||
|
|
||||||
|
get 'whatever/foo/bar/show/1'
|
||||||
|
assert_equal 'foo/bar#show', @response.body
|
||||||
|
|
||||||
|
assert_equal 'http://www.example.com/whatever/foo/bar/show',
|
||||||
|
url_for(:controller => "foo/bar", :action => "show")
|
||||||
|
|
||||||
|
assert_equal 'http://www.example.com/whatever/foo/bar/show/1',
|
||||||
|
url_for(:controller => "foo/bar", :action => "show", :id => '1')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def test_assert_recognizes_account_overview
|
def test_assert_recognizes_account_overview
|
||||||
with_test_routes do
|
with_test_routes do
|
||||||
assert_recognizes({:controller => "account", :action => "overview"}, "/account/overview")
|
assert_recognizes({:controller => "account", :action => "overview"}, "/account/overview")
|
||||||
|
|
Loading…
Reference in a new issue