How To Hack Devise and Sign Up Your Users via Ajax In Rails
Let’s say you want your users to sign up in order to leave a comment or do whatever other actions you want in your application. Well, it’s not a great experience if the user clicks on “Leave a Comment”, then gets redirected to the sign up page, then gets redirected back. What if, instead, you can get a nice sign up modal to pop up. The user signs up, the modal is hidden, and the user can leave a comment!
Well, this is exactly the functionality I was working on in my app, so I wanted to share how you can do it too.
Generate Devise Views
First, in case you’re not familiar with it, devise is an incredible rails gem that handles all user authentication stuff. Once you got the basic devise functionality setup by following the README, make sure to generate the devise views inside your own app as well. The wiki page explains it all, so I won’t go into detail on this part of the process.
Configure Devise To Accept JSON
First, we need to configure devise to accept a JSON request, which is what our AJAX will send it. To do that, simply change the following variables in your config/initializers/devise.rb file:
# config/initializers/devise.rb config.http_authenticatable_on_xhr = false config.navigational_formats = ["*/*", :html, :json]
Create A Registrations Controller
The sign up action happens from the devise registrations controller. You’ll have to overwrite the controller to respond with JSON. So just create a registrations controller, and here is an example of how you would overwrite it:
# app/controllers/registrations_controller.rb class RegistrationsController < Devise::RegistrationsController def create build_resource if resource.save if resource.active_for_authentication? set_flash_message :notice, :signed_up if is_navigational_format? sign_up(resource_name, resource) return render :json => {:success => true} else set_flash_message :notice, :"signed_up_but_#{resource.inactive_message}" if is_navigational_format? expire_session_data_after_sign_in! return render :json => {:success => true} end else clean_up_passwords resource return render :json => {:success => false} end end # Signs in a user on sign up. You can overwrite this method in your own # RegistrationsController. def sign_up(resource_name, resource) sign_in(resource_name, resource) end end
Keep in mind that you now have control over the json that will be returned, so you can customize it to whatever you think it should be, especially for when the sign up is unsuccessful due to validation errors.
Add Devise Mappings To Your Application Helper
Since devise is using the generic “resource” in it’s code, you need to map your user model to the devise resource. You can do that by adding the following to your applications helper or the helper file for the controller where you’d like to use the devise ajax functionality:
# app/helpers/application_helper.rb module ApplicationsHelper def resource_name :user end def resource @resource ||= User.new end def devise_mapping @devise_mapping ||= Devise.mappings[:user] end end
Redirect Routes!
In case you’ve tried the above steps and nothing changed, that’s because you have to redirect devise to use your controller instead of it’s own. This is done very easily in your routes file:
# config/routes.rb devise_for :users, :controllers => {registrations: 'registrations'}
Update Your Sign Up Form
This is where those devise views in your rails app become handy. The form_for helper accepts a :remote => true options, which automatically posts the form remotely (via ajax) for you, and the :format => :json option converts your form information into json. Here is what your form should look like (if you’re using haml):
- # app/views/devise/registrations/new.html.haml %h2 Sign up = form_for(resource, :as => resource_name, :url => registration_path(resource_name), :html => {:id => "sign_up_user"}, :format => :json, :remote => true ) do |f| = devise_error_messages! %div = f.text_field :username, placeholder: "name" %div = f.email_field :email, placeholder: 'email' %div = f.password_field :password, placeholder: "password" %div = f.password_field :password_confirmation, placeholder: "password confirmation" %div= f.submit "Sign up", class: 'btn btn-success btn-medium'
Add The JavaScripts
And, of course, you need JavaScript to catch the ajax response from your server and handle it on the client side. Here is an example (in coffeescript):
# app/assets/javascripts/application.js.coffee $("form#sign_up_user").bind "ajax:success", (e, data, status, xhr) -> if data.success $('#sign_up').modal('hide') $('#sign_up_button').hide() $('#submit_comment').slideToggle(1000, "easeOutBack" ) else alert('failure!')
And now, everything should be working! Magic, right?!