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.

Leave a Comment

Your email address will not be published. Required fields are marked *