Managing Multiple Users: Single Table Inheritance and Role-Based Authorization in Rails
Tags: rails, ruby, postgres, webdev
Published: 2023-08-13 01:48:25

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
```

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
```

#### 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](https://github.com/CanCanCommunity/cancancan) or [Pundit](https://rubygems.org/gems/pundit/versions/1.1.0)). 

#### 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
```

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.