HTTP redirects in a Turbo-Rails app
posted by Ayush Newatia
15 September, 2022
This post was extracted and adapted from The Rails and Hotwire Codex.
In Rails, a conventional redirect in a controller action looks like:
def create
# ...
redirect_to root_path
end
This sends a response with an HTTP status code of 302 Found
.
The Turbo docs, however, state that the response to a form submission must be 303 See Other
unless something goes wrong.
The reason behind this is the spec for 302 Found
states that the redirected request should use the same HTTP method as the original request. Due to legacy reasons, the fetch
(used by Turbo under the hood) implementation in most browsers will respond to a 302 Found
response from a POST
request by issuing a GET
request to the redirected path. For all other HTTP methods, it will use the same method as the original request when redirecting.
Responding with a 303 See Other
status ensures that the method used for the redirect is always GET
.
def create
# ...
redirect_to root_path, status: :see_other
end
This anomaly with redirect codes isn’t a massive problem in Rails because Rails forms use the only two browser native methods: GET
and POST
. Forms with different methods are rendered to use POST
with the method inserted as a hidden field which is then parsed by Rails before the request hits a controller.
For example:
<%= form_with(url: posts_path, method: :put) do |form| %>
<%= form.text_field :name %>
<%= form.submit %>
<% end %>
will render
<form action="/posts" accept-charset="UTF-8" method="post">
<input type="hidden" name="_method" value="put">
<input type="hidden" name="authenticity_token" value="...">
<input type="text" name="name" id="name">
<input type="submit" name="commit" value="Save">
</form>
However, this could potentially change as Rails integrates more tightly with Turbo. There are already a couple of use cases where 302
redirects can be problematic.
Turbo submits the form using the method specified in the formmethod
attribute when it’s defined. The below form would submit using a PUT
request.
<%= form_with(url: posts_path) do |form| %>
<%= form.text_field :name %>
<%= form.submit formmethod: :put %>
<% end %>
If your controller redirected using a 302 Found
, that would result in another PUT
request to the redirected path.
Since Turbo intercepts form submissions, you can use any HTTP method in a <form>
’s method
attribute, not just GET
and POST
. This form will also submit with a PUT
:
<form action="/posts" accept-charset="UTF-8" method="put">
<input type="text" name="name" id="name">
<input type="submit" value="Save">
</form>
Rails form helpers won’t render this, but it’s good to know what’s possible!
As such, I believe it’s good practice to ALWAYS redirect with a 303 See Other
status code when using Turbo in a Rails app.
If you liked this post, check out my book, The Rails and Hotwire Codex, to level-up your Rails and Hotwire skills!