Turbolinks with Devise

If you’ve ever tried to have your Devise forms submit via AJAX, then you may have found that the resources to accomplish this are kind of spread out all over the internet.

Consider this my humble effort to bring all those bits and pieces of information together in a way that I wish was available when I took on this task. Without further ado, here are the steps I took to use Devise with Turbolinks.

Note: This assumes you already have a fairly default configuration of Devise.

First, we have to configure Devise to allow us to use jquery-ujs and SJR responses.

# config/initializers/devise.rb

# If 401 status code should be returned for AJAX requests. True by default.
config.http_authenticatable_on_xhr = false

# ==> Navigation configuration
# Lists the formats that should be treated as navigational. Formats like
# :html, should redirect to the sign in page when the user does not have
# access, but formats like :xml or :json, should return 401.
# If you have any extra navigational formats, like :iphone or :mobile, you
# should add them to the navigational formats lists.
# The "*/*" below is required to match Internet Explorer requests.
config.navigational_formats = ['*/*', :html, :js]
# app/controllers/application_controller.rb

class ApplicationController < ActionController::Base
  respond_to :html, :js, if: :devise_controller?

On to the views, if you have not already copied the devise views into your application, let’s go ahead and do that.

rails generate devise:views

Now we’ll use jquery-ujs to easily make all the devise view forms submit via AJAX. Here’s an example.

<!-- app/views/devise/registrations/new.html.erb -->

<%= form_for(resource, as: resource_name, url: registration_path(resource_name), remote: true) do |f| %>

Voila! Moving forward, we come to the SJR responses. Since our forms are now submitted via AJAX, we need to handle those responses properly. Here’s an example.

// app/views/devise/registrations/create.js.erb

<% if user_signed_in?  %>
  Turbolinks.visit("<%= response.location %>", { "action": "replace" })
<% else %>
  $('[data-behavior="devise-error-messages"]').html('<%= j devise_error_messages! %>');
  $('[data-behavior="flashes"]').html('<%= j render "layouts/flashes", flash: flash %>');
<% end %>

For most of the responses you’ll only have to deal with create/update, but when you try to write the response for your login, you’ll find an odd issue. It’s a small quirk that just means we’ll have to separate our logic from the previous example.

// app/views/devise/sessions/new.js.erb

$('[data-behavior="devise-error-messages"]').html('<%= j devise_error_messages! %>');
$('[data-behavior="flashes"]').html('<%= j render "layouts/flashes", flash: flash %>');
// app/views/devise/sessions/create.js.erb

Turbolinks.visit("<%= response.location %>", { "action": "replace" })

Finally, we’ll set up our flashes such that we get proper error messages from the responses. Let’s add that anywhere it makes sense in your layout.

<!-- app/views/layouts/application.html.erb -->

<div data-behavior="devise-error-messages">
  <%= devise_error_messages! %>
<div data-behavior="flashes">
  <%= render partial: "layouts/flashes", local: { flash: flash } %>
<%= yield %>

And just like that we have Devise working in perfect harmony with Turbolinks!