POST Redirects in Rails
Every so often I see someone on a blog, mailing list, or discussion board asking how to use redirect_to in Rails to send a POST request. The HTTP/1.1 protocol states that browsers should only follow GET or HEAD redirects without prompting for the user’s permission. In practice, most modern browsers just convert all redirects to a GET request. Thus, we can’t rely on a client-side redirect to help us out.
There’s nothing that says we can’t simulate this behavior server-side though. Let’s create a controller method called redirect_post to handle the bulk of the work.
def redirect_post(redirect_post_params)
controller_name = redirect_post_params[:controller]
controller = “#{controller_name.camelize}Controller”.constantize
# Throw out existing params and merge the stored ones
request.parameters.reject! { true }
request.parameters.merge!(redirect_post_params)
controller.process(request, response)
if response.redirected_to
@performed_redirect = true
else
@performed_render = true
end
end
The code should be fairly straightforward. Pass the redirected post params to the controller’s process method and handle the response. If the response redirected then follow that redirect otherwise render the contents of the response.
So how is this useful? Let’s say you’re using RESTful routes and you present a read-only version of a site to anonymous users. If the user clicks a button that would normally create or update some content you want to redirect them to the login screen and then proceed with the action (real-world example). Without the server-side POST redirect we would need the user to login and then retry their previous action.
Here’s some simple authentication code showing how to use redirect_post for this purpose:
def login_required
current_user || store_and_redirect
end
def store_and_redirect(default = new_login_url)
if request.method == :post
session[:return_post_params] = params
end
session[:return_to] = request.request_uri
redirect_to default
end
def redirect_back(default = welcome_path)
return_params = session[:return_post_params]
session[:return_post_params] = nil
if return_params
redirect_post(return_params)
else
redirect_to(session[:return_to] || default)
end
session[:return_to] = nil
end
Basically, store the POST params and inject them back into the request object after a successful login. Change the defaults on the store_and_redirect and redirect_back methods to match your application. Protect the controller methods that require login with a login_required before_filter. After a successful login, call redirect_back.
Now if an anonymous user clicks a button that POSTs to a method requiring login, they get sent through the login process and we process the original POST controller action transparently. There are other alternatives but for our particular use this seemed like the cleanest solution.
What’s your preferred solution?
Edited 5/28: After greater understanding of Rails internal I’ve updated the redirect_post method to fix a bug with the handling of non-200 response codes.
May 26th, 2008 at 10:31 pm
Neat solution!
“#{controller_name.camelize}Controller”.constantize
hmm - looks like Inflector needs a “controllerize”