提交 8ca8a2d7 编写于 作者: A Andrew White

Refactor handling of :action default in routing

The longstanding convention in Rails is that if the :action parameter
is missing or nil then it defaults to 'index'. Up until Rails 5.0.0.beta1
this was handled slightly differently than other routing defaults by
deleting it from the route options and adding it to the recall parameters.

With the recent focus of removing unnecessary duplications this has
exposed a problem in this strategy - we are now mutating the request's
path parameters and causing problems for later url generation. This will
typically affect url_for rather a named url helper since the latter
explicitly pass :controller, :action, etc.

The fix is to add a default for :action in the route class if the path
contains an :action segment and no default is passed. This change also
revealed an issue with the parameterized part expiry in that it doesn't
follow a right to left order - as soon as a dynamic segment is required
then all other segments become required.

Fixes #23019.
上级 3dd9fe5d
* Refactor handling of `:action` default in routing so that the request's
`path_parameters` is not mutated when generating a route.
Fixes #23019.
*Andrew White*
* Add request encoding and response parsing to integration tests. * Add request encoding and response parsing to integration tests.
What previously was: What previously was:
......
...@@ -32,8 +32,13 @@ def generate(name, options, path_parameters, parameterize = nil) ...@@ -32,8 +32,13 @@ def generate(name, options, path_parameters, parameterize = nil)
defaults = route.defaults defaults = route.defaults
required_parts = route.required_parts required_parts = route.required_parts
parameterized_parts.keep_if do |key, value|
(defaults[key].nil? && value.present?) || value.to_s != defaults[key].to_s || required_parts.include?(key) route.parts.reverse_each do |key|
break if defaults[key].nil? && parameterized_parts[key].present?
break if parameterized_parts[key].to_s != defaults[key].to_s
break if required_parts.include?(key)
parameterized_parts.delete(key)
end end
return [route.format(parameterized_parts), params] return [route.format(parameterized_parts), params]
......
...@@ -33,11 +33,11 @@ def reqs ...@@ -33,11 +33,11 @@ def reqs
end end
def controller def controller
requirements[:controller] || ':controller' parts.include?(:controller) ? ':controller' : requirements[:controller]
end end
def action def action
requirements[:action] || ':action' parts.include?(:action) ? ':action' : requirements[:action]
end end
def internal? def internal?
......
...@@ -137,6 +137,10 @@ def initialize(set, ast, defaults, controller, default_action, modyoule, to, for ...@@ -137,6 +137,10 @@ def initialize(set, ast, defaults, controller, default_action, modyoule, to, for
@conditions = Hash[conditions] @conditions = Hash[conditions]
@defaults = formats[:defaults].merge(@defaults).merge(normalize_defaults(options)) @defaults = formats[:defaults].merge(@defaults).merge(normalize_defaults(options))
if path_params.include?(:action) && !@requirements.key?(:action)
@defaults[:action] ||= 'index'
end
@required_defaults = (split_options[:required_defaults] || []).map(&:first) @required_defaults = (split_options[:required_defaults] || []).map(&:first)
end end
......
...@@ -533,12 +533,10 @@ def initialize(named_route, options, recall, set) ...@@ -533,12 +533,10 @@ def initialize(named_route, options, recall, set)
@recall = recall @recall = recall
@set = set @set = set
normalize_recall!
normalize_options! normalize_options!
normalize_controller_action_id! normalize_controller_action_id!
use_relative_controller! use_relative_controller!
normalize_controller! normalize_controller!
normalize_action!
end end
def controller def controller
...@@ -557,11 +555,6 @@ def use_recall_for(key) ...@@ -557,11 +555,6 @@ def use_recall_for(key)
end end
end end
# Set 'index' as default action for recall
def normalize_recall!
@recall[:action] ||= 'index'
end
def normalize_options! def normalize_options!
# If an explicit :controller was given, always make :action explicit # If an explicit :controller was given, always make :action explicit
# too, so that action expiry works as expected for things like # too, so that action expiry works as expected for things like
...@@ -615,13 +608,6 @@ def normalize_controller! ...@@ -615,13 +608,6 @@ def normalize_controller!
end end
end end
# Move 'index' action from options to recall
def normalize_action!
if @options[:action] == 'index'.freeze
@recall[:action] = @options.delete(:action)
end
end
# Generates a path from routes, returns [path, params]. # Generates a path from routes, returns [path, params].
# If no route is generated the formatter will raise ActionController::UrlGenerationError # If no route is generated the formatter will raise ActionController::UrlGenerationError
def generate def generate
......
...@@ -1964,11 +1964,11 @@ def test_generate_extras ...@@ -1964,11 +1964,11 @@ def test_generate_extras
def test_extras def test_extras
params = {:controller => 'people'} params = {:controller => 'people'}
assert_equal [], @routes.extra_keys(params) assert_equal [], @routes.extra_keys(params)
assert_equal({:controller => 'people'}, params) assert_equal({:controller => 'people', :action => 'index'}, params)
params = {:controller => 'people', :foo => 'bar'} params = {:controller => 'people', :foo => 'bar'}
assert_equal [:foo], @routes.extra_keys(params) assert_equal [:foo], @routes.extra_keys(params)
assert_equal({:controller => 'people', :foo => 'bar'}, params) assert_equal({:controller => 'people', :action => 'index', :foo => 'bar'}, params)
params = {:controller => 'people', :action => 'create', :person => { :name => 'Josh'}} params = {:controller => 'people', :action => 'create', :person => { :name => 'Josh'}}
assert_equal [:person], @routes.extra_keys(params) assert_equal [:person], @routes.extra_keys(params)
......
...@@ -339,7 +339,7 @@ def test_regression_route_with_controller_regexp ...@@ -339,7 +339,7 @@ def test_regression_route_with_controller_regexp
end end
assert_equal ["Prefix Verb URI Pattern Controller#Action", assert_equal ["Prefix Verb URI Pattern Controller#Action",
" GET /:controller(/:action) (?-mix:api\\/[^\\/]+)#:action"], output " GET /:controller(/:action) :controller#:action"], output
end end
def test_inspect_routes_shows_resources_route_when_assets_disabled def test_inspect_routes_shows_resources_route_when_assets_disabled
......
...@@ -3964,16 +3964,6 @@ def app; APP end ...@@ -3964,16 +3964,6 @@ def app; APP end
end end
class TestMultipleNestedController < ActionDispatch::IntegrationTest class TestMultipleNestedController < ActionDispatch::IntegrationTest
module ::Foo
module Bar
class BazController < ActionController::Base
def index
render :inline => "<%= url_for :controller => '/pooh', :action => 'index' %>"
end
end
end
end
Routes = ActionDispatch::Routing::RouteSet.new.tap do |app| Routes = ActionDispatch::Routing::RouteSet.new.tap do |app|
app.draw do app.draw do
namespace :foo do namespace :foo do
...@@ -3985,7 +3975,18 @@ def index ...@@ -3985,7 +3975,18 @@ def index
end end
end end
module ::Foo
module Bar
class BazController < ActionController::Base
include Routes.url_helpers include Routes.url_helpers
def index
render :inline => "<%= url_for :controller => '/pooh', :action => 'index' %>"
end
end
end
end
APP = build_app Routes APP = build_app Routes
def app; APP end def app; APP end
...@@ -4717,3 +4718,42 @@ def assert_params(params) ...@@ -4717,3 +4718,42 @@ def assert_params(params)
assert_equal(params, request.path_parameters) assert_equal(params, request.path_parameters)
end end
end end
class TestPathParameters < ActionDispatch::IntegrationTest
Routes = ActionDispatch::Routing::RouteSet.new.tap do |app|
app.draw do
scope module: 'test_path_parameters' do
scope ':locale', locale: /en|ar/ do
root to: 'home#index'
get '/about', to: 'pages#about'
end
end
get ':controller(/:action/(:id))'
end
end
class HomeController < ActionController::Base
include Routes.url_helpers
def index
render inline: "<%= root_path %>"
end
end
class PagesController < ActionController::Base
include Routes.url_helpers
def about
render inline: "<%= root_path(locale: :ar) %> | <%= url_for(locale: :ar) %>"
end
end
APP = build_app Routes
def app; APP end
def test_path_parameters_are_not_mutated
get '/en/about'
assert_equal "/ar | /ar/about", @response.body
end
end
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册