Skip to content

enjaku4/rabarber

Rabarber: Simple Role-Based Authorization for Rails

Gem Version Downloads Github Actions badge License

Rabarber is a role-based authorization library for Ruby on Rails. It provides a set of tools for managing user roles and defining access rules, with support for multi-tenancy through context.

Example of Usage:

Consider a CRM system where users with different roles have distinct access levels. For instance, the role accountant can access and manage invoice data, while the role analyst can only view it. Here's how you'd define that with Rabarber:

class InvoicesController < ApplicationController
  grant_access roles: :accountant

  grant_access action: :index, roles: :analyst
  def index
    # Accessible to accountant and analyst
  end

  def update
    # Accessible to accountant only
  end
end

Table of Contents

Gem Usage:

Community Resources:

Installation

Add Rabarber to your Gemfile:

gem "rabarber"

Install the gem:

bundle install

Generate the migration to store roles (replace users with your table name if different):

# For standard integer IDs
rails generate rabarber:roles users

# For UUID primary keys
rails generate rabarber:roles users --uuid

Run the migration:

rails db:migrate

Configuration

Create an initializer to customize Rabarber's behavior (optional):

Rabarber.configure do |config|
  # Enable/disable role caching (default: true)
  config.cache_enabled = true
  # Method to access current user (default: :current_user)
  config.current_user_method = :current_user
  # User model name (default: "User")
  config.user_model_name = "User"
end

Roles are cached by default for better performance. Clear the cache manually when needed:

Rabarber::Cache.clear

User Role Methods

Your user model is automatically augmented with role management methods:

Role Assignment

# Assign roles (creates roles if they don't exist)
user.assign_roles(:admin, :manager)

# Assign only existing roles (don't create new ones)
user.assign_roles(:accountant, :manager, create_new: false)

# Revoke specific roles
user.revoke_roles(:admin, :manager)

# Revoke all roles
user.revoke_all_roles

All role assignment and revocation methods return the list of roles currently assigned to the user.

Role Queries

# Check if user has any of the specified roles
user.has_role?(:accountant, :manager)

# Get user's roles in the global context
user.roles

# Get all user's roles grouped by context
user.all_roles

# Get users with any of the specified roles
User.with_role(:admin, :manager)

Direct Role Management

You can also directly manage roles available in the application:

# Create a new role
Rabarber.create_role(:admin)
# => true if created, false if already exists

# Rename a role
Rabarber.rename_role(:admin, :administrator)
# => true if renamed, false if new name exists or role is assigned

# Force rename even if role is assigned
Rabarber.rename_role(:admin, :administrator, force: true)

# Remove a role
Rabarber.delete_role(:admin)
# => true if deleted, false if role is assigned

# Force deletion even if role is assigned
Rabarber.delete_role(:admin, force: true)

# List available roles in the global context
Rabarber.roles

# List all available roles grouped by context
Rabarber.all_roles

Authorization

Setup

Include Rabarber::Authorization module in your controllers and configure authorization:

class ApplicationController < ActionController::Base
  include Rabarber::Authorization

  # Enable authorization check for all actions in all controllers by default
  with_authorization
end

You can also enable authorization checks selectively. Both with_authorization and skip_authorization work exactly the same as Rails' before_action and skip_before_action methods:

class TicketsController < ApplicationController
  # Skip authorization for specific actions
  skip_authorization only: [:index, :show]
end

class InvoicesController < ApplicationController
  # Enable authorization for all actions except index
  with_authorization except: [:index]
end

Authorization requires an authenticated user. Rabarber will raise an error if no user is found via the configured current_user_method. Ensure authentication happens before authorization.

Authorization Rules

Rabarber follows a deny-by-default principle: if no grant_access rule is defined for an action or controller, access is denied to everyone.

Define authorization rules using grant_access:

class TicketsController < ApplicationController
  # Controller-wide access
  grant_access roles: :admin

  # Action-specific access
  grant_access action: :index, roles: [:manager, :support]
  def index
    # Accessible to admin, manager, and support roles
  end

  def destroy
    # Accessible to admin role only
  end
end

Authorization rules are additive - they combine across inheritance chains and when defined multiple times for the same action or controller:

class BaseController < ApplicationController
  # Admin can access everything
  grant_access roles: :admin
end

class InvoicesController < BaseController
  # Accountant can also access InvoicesController (along with admin)
  grant_access roles: :accountant

  grant_access action: :index, roles: :manager
  grant_access action: :index, roles: :supervisor
  def index
    # Index is accessible to admin, accountant, manager, and supervisor
  end
end

It's possible to omit roles to allow unrestricted access:

class UnrestrictedController < ApplicationController
  # Allow all users to access all actions
  grant_access
end

class MixedController < ApplicationController
  # Unrestricted index action
  grant_access action: :index
  def index
    # Accessible to all users
  end

  # Restricted show action
  grant_access action: :show, roles: :member
  def show
    # Accessible to members only
  end
end

Dynamic Authorization Rules

For more complex scenarios, Rabarber supports dynamic authorization rules:

class OrdersController < ApplicationController
  grant_access roles: :manager, unless: -> { current_user.suspended? }

  grant_access action: :show, roles: :client, if: :user_company_matches_order?
  def show
    # ...
  end

  private

  def user_company_matches_order?
    current_user.company == Order.find(params[:id]).company
  end
end

You can pass a dynamic rule as an if or unless argument, which can be a symbol or a proc. Symbols refer to instance methods, and procs are evaluated in the controller at request time.

Dynamic rules can also be used without roles at all, allowing you to define custom logic or even delegate to custom policy objects:

class InvoicesController < ApplicationController
  grant_access action: :update, unless: -> { invoice.period_closed? }
  def update
    # ...
  end

  grant_access action: :destroy, if: :destroy_allowed?
  def destroy
    # ...
  end

  private

  def destroy_allowed?
    InvoicePolicy.new(current_user).destroy?(invoice)
  end

  def invoice
    @invoice ||= Invoice.find(params[:id])
  end
end

When Unauthorized

By default, when unauthorized, Rabarber will redirect back (HTML format) or return 403 (other formats). You can override when_unauthorized method to customize unauthorized access behavior:

class ApplicationController < ActionController::Base
  include Rabarber::Authorization

  with_authorization

  private

  def when_unauthorized
    # Custom behavior to hide existence of protected resources
    head :not_found
  end
end

The when_unauthorized method can be overridden in any controller to provide controller-specific unauthorized access handling.

Context / Multi-tenancy

Rabarber supports multi-tenancy through its context feature. All Rabarber methods accept a context parameter, allowing you to work with roles within specific scopes. By default, context is nil, meaning roles are global. Context can also be an instance of an ActiveRecord model or a class.

For example, in a project management app, you might want users to have different roles in different projects - someone could be an owner in one project but just a member in another.

Contextual Role Assignment and Queries

# Assign roles within a specific model instance
user.assign_roles(:owner, context: project)
user.assign_roles(:member, context: project)

# Assign roles within a model class
user.assign_roles(:admin, context: Project)

# Check contextual roles
user.has_role?(:owner, context: project)
user.has_role?(:admin, context: Project)

# Revoke roles
user.revoke_roles(:owner, context: project)

# Get user roles
user.roles(context: project)

# Get users with a role
User.with_role(:member, context: project)

Contextual Role Management

# Create a new role within a context
Rabarber.create_role(:admin, context: Project)

# Rename a role within a context
Rabarber.rename_role(:admin, :owner, context: project)

# Remove a contextual role
Rabarber.delete_role(:admin, context: project)

# List available roles within a specific context
Rabarber.roles(context: project)

Contextual Authorization

In authorization rules, in addition to specifying context explicitly, you can also provide a proc or a symbol (similar to dynamic rules):

class ProjectsController < ApplicationController
  grant_access roles: :admin, context: Project

  # Method-based context resolution
  grant_access action: :show, roles: :member, context: :current_project
  def show
    # Accessible to Project admin and members of the current project
  end

  # Proc-based context resolution
  grant_access action: :update, roles: :owner, context: -> { Project.find(params[:id]) }
  def update
    # Accessible to Project admin and owner of the current project
  end

  private

  def current_project
    @current_project ||= Project.find(params[:id])
  end
end

Orphaned Contextual Roles

When a context object is deleted from your database, its associated roles become orphaned and ignored by Rabarber.

To clean up orphaned roles, use:

Rabarber.prune

Context Migrations

When you rename or remove models used as contexts, you need to update Rabarber's stored context data accordingly. Use these irreversible data migrations:

# Rename a context class (e.g., when you rename your Ticket model to Task)
migrate_authorization_context!("Ticket", "Task")

# Remove context data (e.g., when you delete the Ticket model entirely)
delete_authorization_context!("Ticket")

View Helpers

Include view helpers in your application:

module ApplicationHelper
  include Rabarber::Helpers
end

Use conditional rendering based on roles:

<%= visible_to(:admin, :manager) do %>
  <div class="admin-panel">
    <!-- Admin/Manager content -->
  </div>
<% end %>

<%= hidden_from(:guest) do %>
  <div class="member-content">
    <!-- Content hidden from guests -->
  </div>
<% end %>

<%= visible_to(:owner, context: @project) do %>
  <div class="project-owner-panel">
    <!-- Content visible to project owners -->
  </div>
<% end %>

Getting Help and Contributing

Getting Help

Have a question or need assistance? Open a discussion in the discussions section for:

  • Usage questions
  • Implementation guidance
  • Feature suggestions

Reporting Issues

Found a bug? Please create an issue with:

  • A clear description of the problem
  • Steps to reproduce the issue
  • Your environment details (Rails version, Ruby version, etc.)

Contributing Code

Ready to contribute? You can:

  • Fix bugs by submitting pull requests
  • Improve documentation
  • Add new features (please discuss first in the discussions section)

Before contributing, please read the contributing guidelines

License

The gem is available as open source under the terms of the MIT License.

Code of Conduct

Everyone interacting in the Rabarber project is expected to follow the code of conduct.

Old Versions

Only the latest major version is supported. Older versions are obsolete and not maintained, but their READMEs are available here for reference:

v5.x.x | v4.x.x | v3.x.x | v2.x.x | v1.x.x

About

Simple role-based authorization library for Ruby on Rails

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Sponsor this project

 

Contributors

Languages