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
orClient.all
to pull up all the instances of those classes (as opposed toUser.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 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
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)