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

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 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 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 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.

#Gemfille
gem 'devise'
#Terminal
bundle install
rails g devise:install

To setup Devise, the gem recommends the following:

  • Setting a default email URL in the environment initializer ( 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
#config/routes.rb
root to: pages#home"
========================#app/views/layouts/application.html.erb (add above the <%= yield %>)<p class="notice"><%= notice %></p>
<p class="alert"><%= alert %></p>
========================#Terminal
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

#Terminal 
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:

http://localhost:3000/users/sign_in
http://localhost:3000/users/sign_up

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

FULL GIST LINK

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 above.

Step 3: Add the Login Modal to the Navbar

The Devise login form can be found on the page. This form will need to be moved into the partial to render the view as a modal.

Note that the form uses 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>
<div class="modal-footer">
<%= f.button :submit, "Log in", class: "btn btn-primary" %>
</div>
<% 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>
</button>
</div>
<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>
<div class="modal-footer">
<%= f.button :submit, "Log in", class: "btn btn-primary" %>
</div>
<% end %>
</div>
</div>
</div>
</div>

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 variable from the form

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

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

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

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 error when submitting a remote form and the second will allow the controllers to respond to javascript.

#config/initializers/devise.rb
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)

#Terminal 
rails g devise:controllers
#config/routes.rb
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 line below the class, but above the individual actions.

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

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

#app/views/shared/_login_form.html.erb
<%= 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 file on successful submission and 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.

#app/views/devise/sessions/create.js.erb
const modal = document.querySelector('.modal')
$(document).ready(function() {
$('.modal').modal('hide');
})

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:

#app/javascript/packs.application.js 
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:"
#app/views/shared/_login_form.html.erb
<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 button on the navbar needs to be updated to show the option to This can be be done with JavaScript by selecting the button and replacing or by calling 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 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 +562K people. Follow to join our community.

Maria Schuessler

Written by

Full-Stack Developer \\ Traveler \\ Hot Sauce Entrepreneur \\ Le Wagon Mentor \\ Shanghai-based \\ mariacodes.io

The Startup

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

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade