-
Notifications
You must be signed in to change notification settings - Fork 5.5k
How To: Upgrade to Devise 4.9.0 [Hotwire Turbo integration]
https://github.com/heartcombo/devise/blob/main/CHANGELOG.md
This version of Devise adds integration with the Hotwire / Turbo / Turbo-Rails frameworks.
The changelog contains most of the information you need to get started, we'll go over the changes step-by-step here as well.
From the Turbo documentation on form submissions:
After a stateful request from a form submission, Turbo Drive expects the server to return an HTTP 303 redirect response, which it will then follow and use to navigate and update the page without reloading.
The exception to this rule is when the response is rendered with either a 4xx or 5xx status code. This allows form validation errors to be rendered by having the server respond with
422 Unprocessable Entity
and a broken server to display a “Something Went Wrong” screen on a500 Internal Server Error
.
Devise (and Responders) have historically responded with 200 OK
for form validation errors, and redirected with 302 Found
, both of which have been the Rails defaults for rendering and redirects. Since Hotwire/Turbo, Rails has been changing its defaults to better match this new expected behavior of responding with 422 Unprocessable Entity
and redirecting non-GET requests with 303 See Other
.
For better Hotwire / Turbo integration and these new defaults, Devise requires the latest Responders
version (v3.1.0 or higher), which allows configuring the status used for validation error responses (error_status
) and for redirects after POST/PUT/PATCH/DELETE requests (redirect_status
).
Newly generated apps (running the rails generate devise:install
) will be setup with the new defaults already, and if you're upgrading an existing app you can opt-in to the new responses behavior with the following config:
# config/initializers/devise.rb
Devise.setup do |config|
# ...
config.responder.error_status = :unprocessable_entity
config.responder.redirect_status = :see_other
# ...
end
For backwards compatibility, Devise is configured internally with error_status
as :ok
(200 OK
), and redirect_status
as :found
(302 Found
). Configuring it like above would set the error and redirect statuses to 422 Unprocessable Entity
and 303 See Other
respectively, to match the behavior expected by Hotwire/Turbo.
Trying to set these configs on the latest Devise but with an older version of Responders that does not support them, will issue a warning and have no effect.
Note: these defaults may change in future versions of Devise, to better match the Rails + Hotwire/Turbo defaults across the board.
If you have a custom responder set on your application and expect it to affect Devise as well, you may need to override the Devise responder entirely with config.responder = MyApplicationResponder
, so that it uses your custom one.
The main reason Devise uses a custom responder is to be able to configure the statuses as described above, but you can also change that config on your own responder if you want, and Devise will use that. Check the Responders readme for more info on that.
Important: If you have created a custom responder and/or failure app just to customize responses for better Hotwire/Turbo integration, they should no longer be necessary.
Turbo Rails registers a :turbo_stream
mime format to identify its requests. Devise now considers this format as a navigational format, so it works like HTML navigation when using Turbo.
Note: if you relied on :turbo_stream
to be treated as a non-navigational format before, you can reconfigure your navigational_formats
in the Devise initializer file to exclude it.
Most of the below information is relevant for anyone using the default Devise shared views/links (that haven't copied and modified those), and/or if you're moving from rails-ujs to Turbo.
OmniAuth "Sign in with" links where implemented using method: :post
in order to send POST requests to the server that would then redirect to the provider, which required rails-ujs to work. This doesn't work with Turbo (i.e. via data: { turbo_method: :post }
, as it will raise a CORS error when trying to follow the redirect after the POST fetch request.
To keep compatibility with both Hotwire/Turbo and rails-ujs by default in the shared views, these links were changed to buttons (using the button_to
helper) that generate HTML forms with method=POST
. Since rails-ujs is no longer the default for new Rails apps, this allows the OmniAuth buttons to work in both cases.
This only affects apps that are using the default devise/shared/_links.html.erb
partial from Devise with OmniAuth enabled. Most apps should've copied and changed that view and shouldn't be affected, but if you are using the shared links directly, you may need to tweak the new button styling or copy the view and change it back to a link (if you're using rails-ujs) on your app.
The data-confirm
option that adds a confirmation modal to buttons/forms before submission needs to change to data-turbo-confirm
, so that Turbo handles those appropriately. (and not rails-ujs.) Devise has both options set on the "Cancel my account" button for account deletion, for compatibility reasons.
Example:
<%# works with rails-ujs but does not work with Turbo %>
<%= button_to "Cancel my account", registration_path(resource_name), data: { confirm: "Are you sure?" }, method: :delete %>
<%= link_to "Cancel my account", registration_path(resource_name), data: { confirm: "Are you sure?" }, method: :delete %>
<%# works with Turbo but does not work with rails-ujs %>
<%= button_to "Cancel my account", registration_path(resource_name), data: { turbo_confirm: "Are you sure?" }, method: :delete %>
<%= link_to "Cancel my account", registration_path(resource_name), data: { turbo_confirm: "Are you sure?", turbo_method: :delete } %>
<%# works with rails-ujs and Turbo %>
<%= button_to "Cancel my account", registration_path(resource_name), data: { confirm: "Are you sure?", turbo_confirm: "Are you sure?" }, method: :delete %>
<%= link_to "Cancel my account", registration_path(resource_name), data: { confirm: "Are you sure?", turbo_confirm: "Are you sure?", turbo_method: :delete }, method: :delete %>
Devise uses the button_to
approach on its shared links for easier/better compatibility with both. When copying the views to your app, you can just keep the version you need (rails-ujs or Turbo).
Note: turbo-rails v1.3.0+ should be used for data: { turbo_confirm: "<message>" }
compatibility in buttons, prior to that you should use form: { data: { turbo_confirm: "<message>" } }
so that the option gets set on the form wrapping the button. (it includes Turbo 7.2.0, which contains this fix for that behavior.)
The data-method
option that sets the request method for link submissions needs to change to data-turbo-method
. This is not necessary for button_to
or form
s since Turbo can handle those, but links used rails-ujs to submit and need to be changed to match Turbo now.
If you're setting up Devise to sign out via :delete
(the default), and you're using links (instead of buttons wrapped in a form) to sign out with the method: :delete
option, they will need to be updated. (Devise does not provide sign out links/buttons in its shared views.)
Example:
<%# works with rails-ujs but does not work with Turbo %>
<%= link_to "Sign out", destroy_user_session_path, method: :delete %>
<%# works with Turbo %>
<%= button_to "Sign out", destroy_user_session_path, method: :delete %>
<%= link_to "Sign out", destroy_user_session_path, data: { turbo_method: :delete } %>
<%# works with rails-ujs and Turbo %>
<%= button_to "Sign out", destroy_user_session_path, method: :delete %>
<%= link_to "Sign out", destroy_user_session_path, method: :delete, data: { turbo_method: :delete } %>
Make sure your app uses the proper options if you're using links, or switch to buttons wrapped in a form for broader compatibility.
Please report if you run into any issue with the upgrade.