diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index 608512d846055efa2abb686e5477ac923f29cdfd..8b2943af74c6b4d3af888e30e65c5db42367d4f9 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,3 +1,15 @@ +* Deprecate `redirect_to :back` in favor of `redirect_back`, which accepts a + required `fallback_location` argument, thus eliminating the possibility of a + `RedirectBackError`. + + *Derek Prior* + +* Add `redirect_back` method to `ActionController::Redirecting` to provide a + way to safely redirect to the `HTTP_REFERER` if it is present, falling back + to a provided redirect otherwise. + + *Derek Prior* + * `ActionController::TestCase` will be moved to it's own gem in Rails 5.1 With the speed improvements made to `ActionDispatch::IntegrationTest` we no diff --git a/actionpack/lib/action_controller/metal/redirecting.rb b/actionpack/lib/action_controller/metal/redirecting.rb index 0febc905f14e9a50ac52d9f8f75ae37aff1ec5d9..aeecb48f85cd46e4fc16c387256d0a1e3d8167fd 100644 --- a/actionpack/lib/action_controller/metal/redirecting.rb +++ b/actionpack/lib/action_controller/metal/redirecting.rb @@ -20,8 +20,6 @@ module Redirecting # * String starting with protocol:// (like http://) or a protocol relative reference (like //) - Is passed straight through as the target for redirection. # * String not containing a protocol - The current protocol and host is prepended to the string. # * Proc - A block that will be executed in the controller's context. Should return any option accepted by +redirect_to+. - # * :back - Back to the page that issued the request. Useful for forms that are triggered from multiple places. - # Short-hand for redirect_to(request.env["HTTP_REFERER"]) # # === Examples: # @@ -30,7 +28,6 @@ module Redirecting # redirect_to "http://www.rubyonrails.org" # redirect_to "/images/screenshot.jpg" # redirect_to articles_url - # redirect_to :back # redirect_to proc { edit_post_url(@post) } # # The redirection happens as a "302 Found" header unless otherwise specified using the :status option: @@ -61,10 +58,6 @@ module Redirecting # redirect_to post_url(@post), status: 301, flash: { updated_post_id: @post.id } # redirect_to({ action: 'atom' }, alert: "Something serious happened") # - # When using redirect_to :back, if there is no referrer, - # ActionController::RedirectBackError will be raised. You - # may specify some fallback behavior for this case by rescuing - # ActionController::RedirectBackError. def redirect_to(options = {}, response_status = {}) #:doc: raise ActionControllerError.new("Cannot redirect to nil!") unless options raise ActionControllerError.new("Cannot redirect to a parameter hash!") if options.is_a?(ActionController::Parameters) @@ -75,6 +68,26 @@ def redirect_to(options = {}, response_status = {}) #:doc: self.response_body = "You are being redirected." end + # Redirects the browser to the page that issued the request if possible, + # otherwise redirects to provided default fallback location. + # + # redirect_back fallback_location: { action: "show", id: 5 } + # redirect_back fallback_location: post + # redirect_back fallback_location: "http://www.rubyonrails.org" + # redirect_back fallback_location: "/images/screenshot.jpg" + # redirect_back fallback_location: articles_url + # redirect_back fallback_location: proc { edit_post_url(@post) } + # + # All options that can be passed to redirect_to are accepted as + # options and the behavior is indetical. + def redirect_back(fallback_location:, **args) + if referer = request.headers["Referer"] + redirect_to referer, **args + else + redirect_to fallback_location, **args + end + end + def _compute_redirect_to_location(request, options) #:nodoc: case options # The scheme name consist of a letter followed by any combination of @@ -87,6 +100,12 @@ def _compute_redirect_to_location(request, options) #:nodoc: when String request.protocol + request.host_with_port + options when :back + ActiveSupport::Deprecation.warn(<<-MESSAGE.squish) + `redirect_to :back` is deprecated and will be removed from Rails 5.1. + Please use `redirect_back(fallback_location: fallback_location)` where + `fallback_location` represents the location to use if the request has + no HTTP referer information. + MESSAGE request.headers["Referer"] or raise RedirectBackError when Proc _compute_redirect_to_location request, options.call diff --git a/actionpack/test/controller/redirect_test.rb b/actionpack/test/controller/redirect_test.rb index 631ff7d02a202bf8c4346880bcf71911b8f02dbb..21dfd9cd038102bfa0a85a83e98dbebfe5390e14 100644 --- a/actionpack/test/controller/redirect_test.rb +++ b/actionpack/test/controller/redirect_test.rb @@ -42,6 +42,10 @@ def redirect_to_back_with_status redirect_to :back, :status => 307 end + def redirect_back_with_status + redirect_back(fallback_location: "/things/stuff", status: 307) + end + def host_redirect redirect_to :action => "other_host", :only_path => false, :host => 'other.test.host' end @@ -187,7 +191,11 @@ def test_relative_url_redirect_with_status_hash def test_redirect_to_back_with_status @request.env["HTTP_REFERER"] = "http://www.example.com/coming/from" - get :redirect_to_back_with_status + + assert_deprecated do + get :redirect_to_back_with_status + end + assert_response 307 assert_equal "http://www.example.com/coming/from", redirect_to_url end @@ -236,7 +244,11 @@ def test_redirect_to_url_with_network_path_reference def test_redirect_to_back @request.env["HTTP_REFERER"] = "http://www.example.com/coming/from" - get :redirect_to_back + + assert_deprecated do + get :redirect_to_back + end + assert_response :redirect assert_equal "http://www.example.com/coming/from", redirect_to_url end @@ -244,10 +256,32 @@ def test_redirect_to_back def test_redirect_to_back_with_no_referer assert_raise(ActionController::RedirectBackError) { @request.env["HTTP_REFERER"] = nil + + assert_deprecated do + get :redirect_to_back + end + get :redirect_to_back } end + def test_redirect_back + referer = "http://www.example.com/coming/from" + @request.env["HTTP_REFERER"] = referer + + get :redirect_back_with_status + + assert_response 307 + assert_equal referer, redirect_to_url + end + + def test_redirect_back_with_no_referer + get :redirect_back_with_status + + assert_response 307 + assert_equal "http://test.host/things/stuff", redirect_to_url + end + def test_redirect_to_record with_routing do |set| set.draw do diff --git a/guides/source/action_controller_overview.md b/guides/source/action_controller_overview.md index 7e43ba375ae7b6d4a8c772c55b1e0f7f0272ad28..6c622a3643efc193bcd316d1a5ca6b7d4ee8f483 100644 --- a/guides/source/action_controller_overview.md +++ b/guides/source/action_controller_overview.md @@ -1150,7 +1150,7 @@ class ApplicationController < ActionController::Base def user_not_authorized flash[:error] = "You don't have access to this section." - redirect_to :back + redirect_back(fallback_location: root_path) end end diff --git a/guides/source/layouts_and_rendering.md b/guides/source/layouts_and_rendering.md index 71cc030f6a7e1840def86e492ceb02f2d2027924..4bb364c0f8b2eb571b6223923905482259f12e2a 100644 --- a/guides/source/layouts_and_rendering.md +++ b/guides/source/layouts_and_rendering.md @@ -622,10 +622,13 @@ Another way to handle returning responses to an HTTP request is with `redirect_t redirect_to photos_url ``` -You can use `redirect_to` with any arguments that you could use with `link_to` or `url_for`. There's also a special redirect that sends the user back to the page they just came from: +You can use `redirect_back` to return the user to the page they just came from. +This location is pulled from the `HTTP_REFERER` header which is not guaranteed +to be set by the browser, so you must provide the `fallback_location` +to use in this case. ```ruby -redirect_to :back +redirect_back(fallback_location: root_path) ``` #### Getting a Different Redirect Status Code