RoR Roles and Permissions
Authentication answers "Who are you?" Authorisation answers "What are you allowed to do?" After Devise handles login, you need a permission system to control what each user can access and modify. Rails gives you the tools to build this cleanly using roles stored on the User model and enforced in controllers and views.
Simple Role System with an Enum
The simplest role system stores a role as an integer column mapped to named values using Ruby's enum:
Migration:
rails generate migration AddRoleToUsers role:integer
rails db:migrate
Model:
class User < ApplicationRecord
enum role: { viewer: 0, editor: 1, admin: 2 }
end
Rails gives you free helper methods for each role:
user.viewer? # true or false user.editor? # true or false user.admin? # true or false user.role # "admin" user.admin! # sets role to admin and saves User.admins # returns all users with role = admin User.editors # returns all users with role = editor
Set Default Role
class User < ApplicationRecord
enum role: { viewer: 0, editor: 1, admin: 2 }, default: :viewer
end
Every new user starts as a viewer unless you specify otherwise.
Enforce Permissions in Controllers
Write authorization checks directly in your controllers using before actions:
class ArticlesController < ApplicationController
before_action :authenticate_user!
before_action :require_editor!, only: [:new, :create, :edit, :update]
before_action :require_admin!, only: [:destroy]
private
def require_editor!
unless current_user.editor? || current_user.admin?
redirect_to root_path, alert: "You do not have permission to do that."
end
end
def require_admin!
unless current_user.admin?
redirect_to root_path, alert: "Only admins can delete articles."
end
end
end
Move Authorization to ApplicationController
Share permission helpers across all controllers:
class ApplicationController < ActionController::Base
def require_admin!
redirect_to root_path, alert: "Admins only." unless current_user&.admin?
end
def require_editor_or_admin!
unless current_user&.editor? || current_user&.admin?
redirect_to root_path, alert: "Editors and admins only."
end
end
end
Show and Hide Content in Views
<h1><%= @article.title %></h1> <p><%= @article.body %></p> <% if current_user&.editor? || current_user&.admin? %> <%= link_to "Edit", edit_article_path(@article) %> <% end %> <% if current_user&.admin? %> <%= button_to "Delete", article_path(@article), method: :delete %> <% end %>
Hiding a link does not protect the action. Always enforce permissions in the controller as well. A savvy user can craft a direct HTTP request to bypass hidden links.
Using the Pundit Gem for Authorization
As your app grows, scattered permission checks become hard to maintain. Pundit centralizes permissions in Policy classes:
gem "pundit" bundle install rails generate pundit:install
Generate a policy for your Article model:
rails generate pundit:policy article
app/policies/article_policy.rb
class ArticlePolicy < ApplicationPolicy
def index?
true ← anyone can list articles
end
def show?
true ← anyone can read an article
end
def create?
user.editor? || user.admin? ← editors and admins can create
end
def update?
user.admin? || record.user == user ← admin or owner can edit
end
def destroy?
user.admin? ← only admins can delete
end
end
Use policies in controllers:
class ArticlesController < ApplicationController
def show
@article = Article.find(params[:id])
authorize @article
end
def destroy
@article = Article.find(params[:id])
authorize @article
@article.destroy
redirect_to articles_path
end
end
Use policies in views:
<% if policy(@article).update? %> <%= link_to "Edit", edit_article_path(@article) %> <% end %> <% if policy(@article).destroy? %> <%= button_to "Delete", article_path(@article), method: :delete %> <% end %>
Role Hierarchy Diagram
Admin | +-- Can do everything editors can do +-- Can delete any record +-- Can change user roles +-- Can access admin dashboard Editor | +-- Can do everything viewers can do +-- Can create and edit articles +-- Can upload files Viewer | +-- Can read public content +-- Can manage own profile +-- Cannot create or delete content
Assigning Roles in the Admin Panel
class Admin::UsersController < ApplicationController
before_action :require_admin!
def update
@user = User.find(params[:id])
@user.update(role: params[:user][:role])
redirect_to admin_users_path, notice: "Role updated."
end
end
Keep role assignment inside an admin-only controller. Never expose it in a public form that ordinary users can access.
A layered permission system — enums for roles, controller checks for enforcement, Pundit for scalable policy logic, and view conditions for UI — gives you full control over what every user in your application can see and do.
