Integrating Devise With Rolify in Rails

I have searched through tons of StackOverflow questions and blog posts on how to properly integrate Rolify with Devise when registering as a new user through the registration view provided by Devise. All proposed solutions and half-solutions seemed very messy to me, but I found a very simple way to achieve this.

Preparing the Roles

Assuming you already have initialized a Devise User model along with a Rolify Role model, a very common question I see is how should the roles be prepared so that the new users can have a list of already created roles to choose from when registering? In the Rolify documentation, most (if not all) examples involve implicitely creating roles by adding roles to user in the Rails console:

user = User.find(1)
user.add_role :admin

A new admin role will be created if it does not exist already. But in a fresh application, with no users, a list of existing roles should be available in order to register.

Answers to this dilemma involve different approaches. I personally prefer creating them when seeding the database:

# db/seeds.rb

Role.create!(name: 'admin')
Role.create!(name: 'doctor')
Role.create!(name: 'nurse')

Obviously any other dummy data you have in your seeds file should be removed for production.

Adding Roles to the Registration Form

Now let's proceed to add the available roles to the registration form that the users will use to sign up. Depending on your application, you will have to decide whether you need check boxes (multiple roles) or radio buttons (single role). In this example I will go with radio buttons, limiting the user to a single role only.

Using Simple Form, we can render our radio buttons like this:

= simple_form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f|
  = devise_error_messages!

  .form-inputs
    = f.input :email, required: true, autofocus: true
    = f.input :password, required: true
    = f.input :password_confirmation, required: true
    = f.input :roles, as: :radio_buttons, collection: Role.all, value_method: :name, required: true

  .form-actions
    = f.button :submit, t('.sign_up'), class: 'btn btn-success'

Notice that we are pulling and displaying all the available roles. This will include the admin role, which you might not want. This can easily be solved using the handy where.not:

= f.input :roles, as: :radio_buttons, collection: Role.where.not(name: 'admin'), value_method: :name, required: true

Additionally, the value_method is very important. We will use the role's name to pass as a parameter value to the Devise controller. This will allow for easier adding of the role to the user.

Customizing the Devise Controller

When the form is submitted by the user, it will go through Devise's registration controller, which will proceed to do all the registration magic. However, after the user is successfully registered, we want this controller to add the role we passed in the form.

We can begin by explicitely creating this controller and "overwriting" its create method:

# app/controllers/registrations_controller.rb

class RegistrationsController < Devise::RegistrationsController
  before_action :configure_sign_up_params, only: [:create]

  # POST /resource
  def create
    super
    resource.add_role(params[:user][:roles])
  end

  protected

  # If you have extra params to permit, append them to the sanitizer.
  def configure_sign_up_params
    devise_parameter_sanitizer.permit(:sign_up, keys: [roles: []])
  end

end

Technically we aren't really overwriting the controller, since the controller's original create method is still being called by using the super keyword. After this is done, we proceed to add the role.

If you are passing many roles (checkboxes) to the controller as parameters, you will need to adjust this part accordingly.

Lastly we also sanitize this extra roles parameter inside a protected method.

Don't Forget the Routes

We will need to edit the devise route to point to this new registrations controller we created:

# routes.rb

Rails.application.routes.draw do

  devise_for :users, controllers: { registrations: 'registrations' }

end

And that's it! The selected role in the form should now be properly added to the newly registered user upon successful registration. The best part of all is that we do not need to re-write the registration controller's create logic thanks to the handy super.



Comments 32   Comments

comments powered by Disqus