Hack your Rails 5 Signup Form with Ajax

Add a signup form to every page of your app with a Bootstrap modal

Maria Schuessler
Sep 29, 2019 · 6 min read
Image for post
Image for post

I made this tutorial because I hate the way a signup form can ruin a user’s experience.

Say you’re on a website, trying to make an order or leave a comment, and the application redirects you to a sign-in page. Suddenly, you lose your place in the application, your train of thought, and worst of all, you’re likely redirected to the home page after signing up. One way to solve this issue is through adding a sign in modal to seamlessly log the user in, without ever leaving the page.

User Authentication in Rails

The easiest way to handle authentication in Rails 5 (or 6!) is by using the Devise gem. I’ve written about Devise before — it’s a simple to set up a framework that allows you to build an authentication system from the ground up in less than 10 minutes.

In this tutorial, we’ll tackle the three components of adding a signup form to any page of your application:

  • Moving the Devise login from its designated page into a Bootstrap modal
  • Configuring Devise to sign a user in with AJAX
  • Rendering an error message if the user is not signed in successfully

Let’s get started!

Step 1: Create the Rails app and set up Devise

I’m starting with a simple Rails app with a postgreSQL database and webpack enabled.

The --webpack flag is optional. Because Bootstrap modals have a dependency on jQuery, I’ll need to add it into my application to close and open the modals. If you are not using webpacker and yarn to add jQuery , make sure to add the jquery-rails gem and configure it through the asset pipeline.

rails new devise-modal-demo --database=postgresql --webpack
cd devise-modal-demo
rails db:create

Add Devise to the Gemfile and generate the gem configurations.

gem 'devise'
bundle install
rails g devise:install

To setup Devise, the gem recommends the following:

  • Setting a default email URL in the environment initializer (localhost for development, the site URL for production)
  • Making sure there is a root path in the application routes
  • Adding flash notices and alerts to the views
  • Generating the views for Devise (optional, but we will need this later to modify the form)
#config/environments/development.rbconfig.action_mailer.default_url_options = { host: 'localhost', port: 3000 }========================#Terminal
rails g controller pages home
root to: pages#home"
========================#app/views/layouts/application.html.erb (add above the <%= yield %>)<p class="notice"><%= notice %></p>
<p class="alert"><%= alert %></p>
rails g devise:views

For additional configurations, take a look at the Getting Started portion of the Devise wiki on GitHub.

Step 2: Create the User Model & Add Login/Logout Logic

First, generate the User model with Devise

rails g devise User
rails db:migrate

Devise works out of the box by creating a user model with email/password authentication. From this moment, authentication is set up and ready to go — you just need a link to the sign in and sign up pages generated by the gem.

You can find the login and signup pages at the following URLs:


For this, you will need to add a navbar with login/logout logic.


This part is optional if you already have a navbar template, so I will include the components for the navigation and the steps to add bootstrap to the application in the gist above.

Step 3: Add the Login Modal to the Navbar

The Devise login form can be found on the app/views/devise/sessions/new.html.erb page. This form will need to be moved into the partial to render the view as a modal.

Note that the form uses resource to refer to the User, because the gem can be configured with any model name (user, customer, teacher, student, admin, etc..).

#app/views/devise/sessions/new.html.erb<%= simple_form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>
<div class="form-inputs">
<%= f.input :email,
required: false,
autofocus: true,
input_html: { autocomplete: "email" } %>
<%= f.input :password,
required: false,
input_html: { autocomplete: "current-password" } %>
<%= f.input :remember_me, as: :boolean if devise_mapping.rememberable? %>
<div class="modal-footer">
<%= f.button :submit, "Log in", class: "btn btn-primary" %>
<% end %>

I’m using the Bootstrap 4 Modal component and incorporating the signup form inside of it:

#app/views/shared/_login_form.rb<div class="modal fade bd-example-modal-lg"tabindex="-1" role="dialog" aria-labelledby="myLargeModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">Please Log in</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
<div class="modal-body">
<%= simple_form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>
<div class="form-inputs">
<%= f.input :email,
required: false,
autofocus: true,
input_html: { autocomplete: "email" } %>
<%= f.input :password,
required: false,
input_html: { autocomplete: "current-password" } %>
<%= f.input :remember_me, as: :boolean if devise_mapping.rememberable? %>
<div class="modal-footer">
<%= f.button :submit, "Log in", class: "btn btn-primary" %>
<% end %>

To trigger opening the modal, I am rendering it inside of the partial that renders the navbar.

#app/views/shared/_navbar.html.erb<-- Navbar End -->
<%= render 'shared/login_form' %>
#alter the login link to trigger the opening of the modal
<%= link_to "Login", "", data: {toggle: "modal", target: ".bd-example-modal-lg"} %>

The final step is adding the Devise mapping to the application controller, to allow the views to understand the resource variable from the form

#app/controllers/application_controller.rbhelper_method :resource_name, :resource, :devise_mapping, :resource_classdef resource_name

def resource
@resource ||= User.new
def resource_class

def devise_mapping
@devise_mapping ||= Devise.mappings[:user]

Step 4: Add AJAX to submit the form remotely

The goal of this tutorial is to submit the form using Asynchronous JavaScript, which is by default not allowed in Devise. Let’s change that by modifying the gem initializer file. The first line will keep Devise from throwing a 401 Unauthorized error when submitting a remote form and the second will allow the controllers to respond to javascript.

config.http_authenticatable_on_xhr = false
config.navigational_formats = [:"*/*", "*/*", :html, :js]

The next step is going to be a lot of configuration work, to add one line of code. But it’s necessary to make the JavaScript work. Run the following command to generate the controllers we’ll need to modify (typically hidden inside the gem)

rails g devise:controllers
devise_for :users, controllers: {
sessions: 'users/sessions'

This generates every controller for every module of Devise. You can add the Sessions and Registrations controllers manually if you’re more comfortable with this step. 😉

Add the respond_to :html, :js line below the class, but above the individual actions.

#app/controllers/users/sessions_controller.rbclass Users::SessionsController < Devise::SessionsController  respond_to :html, :js

Finally, add the remote: true flag to the form, to enable you to submit with JavaScript

<%= simple_form_for(resource_name, :url => session_path(resource_name), remote: true) do |f| %>

Step 5: Add the JavaScript Response

The final steps are to make the form respond with JavaScript. Because of the previous steps, the form will now call the create.js.erb file on successful submission and new.js.erb file on failure.

When the form submits successfully, we want to close the modal and return to the page. Because the modal I used came from Bootstrap, I need to use jQuery to hide the modal.

const modal = document.querySelector('.modal')
$(document).ready(function() {

If you are getting a “$ is not a function” or a “jQuery is not a function” error in your Console, you may need to add these lines to your webpacker file:

import JQuery from 'jquery';
window.$ = window.JQuery = JQuery;
import 'bootstrap'

If the form does not submit successfully, render the new, with an error message. I’m selecting a div above the form and giving the generic ‘Incorrect Email/Password message’ upon failure.

#app/views/devise/sessions/new.js.erbconst errorMessage = document.querySelector('.errors')
errorMessage.innerHTML = "Incorrect Email/Password. \<br>Please review the errors below:"
<div class="errors"></div>
<--- add this line above the form --->

Phew! That’s a lot of steps. But if you got this far, you’re done!

GitHub Repository Link Here: https://github.com/maltyeva/devise-modal-demo
Heroku Demo App Link Here: https://devise-modal-demo.herokuapp.com/

A few caveats

  • Once the user is logged in via JavaScript, the login button on the navbar needs to be updated to show the option to logout. This can be be done with JavaScript by selecting the button and replacing or by calling window.reload() to refresh the page (but that would defeat the purpose of using pure js for logging 😉)
  • This demo is for creating a modal to sign in, but you can follow the instructions to set up a form to register/sign in the user, just add the respond_to :js line to the registrations controller.
  • I am only showing an error message above the modal form, but you can add additional query selectors to select the inputs and turn them a different colour or add any other visual effects.

Thank you for reading! Let me know below what you’d like to see a tutorial on next.

The Startup

Medium's largest active publication, followed by +718K people. Follow to join our community.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store