Rails: Single Table Inheritance with Devise
How to quickly set up two different types of users with Rails and Devise.
Sometimes we need to have different types of Users in our app. Of course, we could generate two different Devise models and have distinct signup and login flows, but there’s more than one way of achieving a similar result. In this tutorial, we’ll explore Rails’ Single Table Inheritance (STI) on a User devise model.
Let’s say that we have an app where the User model has the email, password, and other Devise related attributes, plus a first_name and last_name. Now, let’s imagine that there is one type of User we’ll call Customer and another one named Seller. For the purpose of this example, Customers can sign up for the application whereas Sellers are created by the application owners and are not able to directly sign up on the web app.
The first thing we want to do is add a column to the Users table named type. This is a reserved column in Rails used to implement STI and it will store a string with the class name of Customer or Seller.
rails g migration AddTypeToUsers type:string
rails db:migrate
We can also add these two auxiliary methods to the User class for convenience:
class User < ApplicationRecord
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable def customer?
type == 'Customer'
end def seller?
type == 'Seller'
end
end
Then we create the classes Customer and Seller which will inherit from User.
# app/models/customer.rb
class Customer < User
# ...
end# app/models/seller.rb
class Seller < User
# ...
end
Even though the instances of Customer and Seller will still be stored in the Users table, we’re now able to use ActiveRecord’s query interface out of the box as follows:
Customer.all # return all Users of the type Customer
Customer.count # return the number of Customers in the DBSeller.all # return all Users of the type Seller
Select.count # return the number of Sellers in the DB
We’ll now override Devise’s registration controller create action to ensure that any User that signs up through the application will automatically be of the Customer type.
# app/controllers/customers/registrations_controller.rbclass Customers::RegistrationsController < Devise::RegistrationsController
before_action :configure_permitted_parameters def create
params[:user] = params[:user]&.merge(type: 'Customer')
super
end protected def configure_permitted_parameters
devise_parameter_sanitizer.permit(:sign_up, keys: %i[type])
end
end
And we’ll need to tell the devise routes to use our own custom RegistrationsController:
Rails.application.routes.draw do
devise_for :users, controllers: {
registrations: 'customers/registrations'
}
end
That’s it. We’ve now got a system where every user who signs up will be a Customer and with Single Table Inheritance, we can add behaviour that is specific to either the Customer or Seller model by placing the code in the respective class.