Rails: Single Table Inheritance with Devise

How to quickly set up two different types of users with Rails and Devise.

Photo by Katka Pavlickova on Unsplash

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

--

--

Web development in Ruby on Rails, React, Vue.js and Elixir

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Rui Freitas

Rui Freitas

Lead Teacher @ Le Wagon | Web Developer @ Light the Fuse and Run: http://lightthefuse.run/ | Photographer @ Rod Loboz: https://blog.rodloboz.com/