DEV Community

Cover image for Managing Multiple Users: Single Table Inheritance and Role-Based Authorization in Rails
Kate Bennert
Kate Bennert

Posted on

Managing Multiple Users: Single Table Inheritance and Role-Based Authorization in Rails

One of the things about web development that keeps me coming back for more is the fact that there are so many different ways to solve a single problem. (This is also something about web development that I find incredibly overwhelming depending on the day, but that day is not today!) The problem of how to handle multiple types of users in Rails is no exception.

I tend to approach challenges like this with an "experiential education" mindset in that when I experience a problem, I educate myself on how to fix it. This is probably the same thing as "trial and error," but I feel like there is more nuance to it.

My personal journey with creating an application that catered to multiple types of users started with a decent understanding of how my data was interrelated, but ended up with not only a much better understanding of my entire database but also a streamlined organizational structure that made sense for my project.

That said I am very much still learning and this is what made sense to me at this point in my career and learning journey. I always welcome comments and suggestions!

The app I'm working on is designed to create a space for event planners (think weddings and large corporate Events) to collaborate with clients on events that they are planning together.

To manage this User/Client/Planner relationship, I used:

  • Single Table Inheritance to create to subclasses of the Users class
  • Role based authorization to ensure that Planners had access to "admin" features that clients did not
  • Some front-end manipulation so that Clients and Planners see a different app when they login

Understanding Single Table Inheritance (STI)

Single Table Inheritance is a way of organizing your Rails application so that different models (in my case the Client and Planner model) can inherit from the same model (Users) and share the same table (also Users) in the database. The benefits of using STI in your application are mainly to:

  • Keep your code DRY by grouping models with similar behavior into one category
  • Help you (the developer) keep things clearer by allowing the separate classes to be called independently of the User class. For example, STI made it possible to call Planner.all or Client.all to pull up all the instances of those classes (as opposed to User.where(role: 'planner')
  • Simplify associations between types of Users

Implementing Single Table Inheritance

Here's how to set up STI in a Rails application using the Users: Clients/Planners example.

Set up your database so that your table has a column for variable classes

You'll need a type or role column to identify each instance of the User class as either Planner or Client.

class CreateUsers < ActiveRecord::Migration[6.1]
  def change
    create_table :users do |t|
      t.string :role
      # other table columns

      t.timestamps
    end
  end
end
Enter fullscreen mode Exit fullscreen mode

Next we'll tell Rails how to use that column.

Set up the models and inheritance

In this case I had a parent model User and two subclasses of that model Client and Planner. Both Client and Planner will inherit from the User model which means they will share any associations or methods in the User model.

class User < ApplicationRecord
   # any common associations or methods

   # establish the column that will define the classes
   self.inheritance_column = :role
end

class Client < User
end

class Planner < User
end
Enter fullscreen mode Exit fullscreen mode

Querying and retrieving data

When you retrieve data from the database, Rails will use the role column established as the inheritance column to return an instance of the subclass. For example,

User.first # returns an instance of Client or Planner depending on the role column

Using data

You can now call on the subclasses just like regular models on your routes. Client.all will return all instances of the Client subclass and Planner.new will create a new instance if the Planner class (which is also a new instance of the user class with the role Planner.

Using Role Definition in Other Aspects of Your Application

Setting up the database for your different roles is only the first step in setting up your application for role-base usage. One of the other big aspects of a role-based application is how your different types of users can interact differently with your app. In my project, Planners have more permissions than Clients and can interact with more of the app. To handle this, I implemented role-based authorization.

Role-Based Authorization in Rails

There are three simple ways to start to implement role-based authorization in your Rails application (amongst some helpers and gems like CanCanCan or Pundit).

1. Set up roles in your database using a "role" or type column like the STI table outlined above

2. Implement a before_action filter in your application controlled

class ApplicationController < ActionController::API

  before_action :planner_auth

  def planner_auth
    render json: { errors: ["Not Authorized"] }, status: :unauthorized unless session[:user_role] == "Planner"
  end

end
Enter fullscreen mode Exit fullscreen mode

This filter remains in place for all controller actions that aren't explicitly skipped: skip_before_action :planner_auth.

3. Use Conditionals in Your View to Limit Access to the App

Although this is important for a smooth user experience, it can't be the only way that you manage that access. Step 2 is important so people without the correct access can step through your app by accident or in a malicious way. In my project, I used conditional rendering to hide certain aspects of the NavBar from Client users.

Role-Based access is a key part of the way web applications are used and maintained. I'm excited to learn more about how developers use these strategies in more complex applications.

Top comments (0)