I was building a marketplace app where I required 2 types of users; normal users and business owners. This is a post on how I create 2 registrations with 1 user model using Devise.
The Devise documentation talks about creating multiple models for different users like having an admins model but I wanted to stick to 1 user model and use roles for defining users as I feel this is a more flexible solution. With the 2 users I needed 2 different registration pages as the UX needs to be different for business owners as they’ll require more of a ‘sales pitch’ as to the benefits for their business.
Devise setup and implementing normal users
The first step was to get Devise up and running for the normal users. This is quite easy and all is explained on the devise github page. In short generate the devise users model and setup the model with the required devise modules.
I wanted to make some changes to the normal users registration process so I also generated the devise controllers and adjusted the routes to point to this controller.
$ rails generate devise:controllers users
# config/routes.rb
devise_for :users, controllers: { registrations: "users/registrations" }
Implementing the business owners users
After setting up the normal users and getting that all working I could then create a secondary registration process for the business owners. I wanted:
- A seperate registration page using a different layout to create more a sales pitch type page
- Require extra user attributes for business owners e.g. full name, contact number
- Assign the ‘business owners’ role to the user upon sign up
- After sign up redirect to a page for setting up their store profile
I generated a new rails controller for the business owners registration and then proceed to write some controller tests using rspec.
require 'rails_helper'
RSpec.describe Users::BusinessOwnersController, type: :controller do
let(:user) { create(:user) }
let(:valid_params_for_create) { {user: { email: "newuser@test.com", f_name: "billy", l_name: "bob", contact_num: "0423123456", password: "password123" }} }
describe "GET #new" do
before(:each) { @request.env["devise.mapping"] = Devise.mappings[:user] }
it "should render new template when no user" do
get :new
expect(response).to render_template(:new)
end
it "should redirect to to edit business owner if user is authenticated" do
sign_in user
get :new
expect(response).to redirect_to edit_business_owner_path
end
end
describe "POST #create" do
before(:each) { @request.env["devise.mapping"] = Devise.mappings[:user] }
before(:each) { allow_any_instance_of(User).to receive(:send_devise_notification) }
it "successfully creates an account" do
post :create, valid_params_for_create
expect(User.count).to eq(1)
end
it "should assign the r_create role to the user" do
post :create, valid_params_for_create
expect(User.last.has_role? :r_create).to eq(true)
end
it "redirects to new business owner after successful create" do
post :create, valid_params_for_create
expect(response).to redirect_to new_business_owner_path
end
it "should redirect if authenticated user tries to create" do
sign_in user
post :create, user: { email: "newuser@test.com", password: "password123" }
expect(response).to redirect_to users_dashboard_path
end
end
describe "GET #edit" do
before(:each) { @request.env["devise.mapping"] = Devise.mappings[:user] }
it "should render edit template when user is authenticated" do
sign_in user
get :edit
expect(response).to render_template(:edit)
end
it "should redirect to user sign up when no user" do
get :edit
expect(response).to redirect_to new_user_session_path
end
end
describe "PATCH #update" do
before(:each) { @request.env["devise.mapping"] = Devise.mappings[:user] }
it "shouldn't allow unauthenticated access" do
patch :update
expect(response).to redirect_to new_user_session_path
end
context "authenticated user" do
before(:each) { sign_in user }
it "should assign the r_create role to the user on successful update" do
patch :update, user: { f_name: "billy", l_name: "bob", contact_num: "0423123456" }
expect(User.last.has_role? :r_create).to eq(true)
end
it "should render edit template if f_name, l_name and contact_num aren't provided" do
patch :update, user: { f_name: "billy" }
expect(response).to render_template(:edit)
end
it "shouldn't assign r_create role without f_name, l_name and contact_num" do
patch :update, user: { f_name: "billy" }
expect(user.has_role? :r_create).to eq(false)
end
it "should render edit if user doesn't have f_name, l_name and contact_num" do
patch :update, user: { f_name: "billy" }
expect(response).to render_template(:edit)
end
it "should redirect to new business owner page after successful update which doesn't require current password" do
patch :update, user: { f_name: "billy", l_name: "bob", contact_num: "0423123456" }
expect(response).to redirect_to new_business_owner_path
end
end
end
end
After creating my tests I then created my routes.
# config/routes.rb
devise_scope :user do
get "business-owners", to: "users/business_owners#new", as: "new_business_owner"
post "business-owners", to: "users/business_owners#create", as: "business_owners"
get "business-owners/edit", to: "users/business_owners#edit", as: "edit_business_owners"
put "business-owners", to: "users/business_owners#update"
patch "business-owners", to: "users/business_owners#update"
end
What I have here is:
- a get route for the business owners registration page
- a post route when the user submits from the get route above
- a get route for edit, this is if a normal user is signed in they can still register as a buisiness owner
- then the last 2 routes are put and patch for the get edit route above
Then I implemented my business owners registration controller.
# app/controllers/users/business_owners_controller.rb
class Users::BusinessOwnersController < Devise::RegistrationsController
prepend_before_action :user_signed_in, only: [:new]
before_action :configure_sign_up_params, only: [:create]
before_action :configure_account_update_params, only: [:update]
layout 'minimum'
def new
super
end
def create
super do |resource|
resource.add_role :business_owner
end
end
def edit
super
end
def update
super do |resource|
if resource.f_name.present? && resource.l_name.present? && resource.contact_num.present?
resource.add_role :business_owner
end
end
end
protected
def configure_sign_up_params
devise_parameter_sanitizer.for(:sign_up) { |u| u.permit(:email, :password, :f_name, :l_name, :contact_num).merge(business_owner: true) }
end
def configure_account_update_params
devise_parameter_sanitizer.for(:account_update) { |u| u.permit(:email, :password, :f_name, :l_name, :contact_num).merge(business_owner: true) }
end
def update_resource(resource, params)
resource.update_without_password(params)
end
def after_sign_up_path_for(resource)
new_business_path
end
def after_update_path_for(resource)
new_business_path
end
def user_signed_in
redirect_to edit_business_owner_path if user_signed_in?
end
end
Firstly this controller is inheriting from Devise::RegistrationsController
that allows me to call super in the methods to access the parents method of the same name. So as you can see the new and edit methods simply call super which prepares a devise resource (a user object).
The other 2 CRUD methods create and update call super but then have some extra logic for assigning the ‘business_owner’ role to the user.
Require more information for a business owner to register
I wanted to create a good UX so for the sign up I only wanted to collect the bare mimimum. For normal users I was just requiring the defaults of email and password but for business owners I needed at least their full name and contact number.
To enforce these extra attributes just for business owners I used a virtual attribute and rails model validations. Firstly I pass this virtual attribute from the controller by merging it in the Devise parameter sanitizer.
devise_parameter_sanitizer.for(:sign_up) { |u| u.permit(:email, :password, :f_name, :l_name, :contact_num).merge(business_owner: true) }
Then in the user model I check if this attribute is there and if so run the validations I want.
# app/models/user.rb
attr_accessor :business_owner
with_options if: :business_owner? do |user|
user.validates :f_name, :l_name, :contact_num, presence: true
end
def business_owner?
business_owner == 'true' || business_owner == true
end
Run tests and volia! I now have 2 different registration flows for the user types with all users going into the 1 model and being easily identified based on roles.
I would love some feedback on this so comment below to go into the draw to win a “ferrari”!*
winner is drawn at random
compeition ends when there are 100 different commenters
the “ferrari” is a digital picture of a ferrari drawn by me in mspaint, which is awesome!