The Ruby on Rails experts
Logo_144 ClearCove Software, Inc. Rails Recipes

This pattern helps you manage permissions in your app in a simple and robust way. In this example we define access permissions on the User class:

  1. Define the permissions in a concern:

    # app/models/user/has_permissions.rb
    module User::HasPermissions
    
      extend ActiveSupport::Concern
    
      module ClassMethods
    
        def creatable_by?(actor)
          true # anybody can sign up for an account
        end
    
        def listable_by?(actor)
          actor.is_admin?
        end
    
      end
    
      def creatable_by?(actor)
        self.class.creatable_by?(actor)
      end
    
      def destroyable_by?(actor)
        return false  if self == actor # can't destroy self
        actor.is_admin?
      end
    
      # Users can edit their own record,
      # admins can edit any user, and
      # managers can update the users they manage
      def updatable_by?(actor)
        # Note: Since these permission methods get called a lot,
        # we try to make them as efficient as possible by first
        # checking the conditions that don't require loading of
        # additional data:
        self == actor || \
        actor.is_admin? || \
        manager == actor
      end
    
      def viewable_by?(actor)
        self == actor || \
        actor.is_admin? || \
        manager == actor
      end
    
    end
    
  2. Include the HasPermissions concern in the User class:

    # app/models/user.rb
    class User < ActiveRecord::Base
    
      include User::HasPermissions
      belongs_to :manager
      # ...
    
    end
    
  3. Check permissions in the controller:

    # app/controllers/users_controller.rb
    class UserController < ApplicationController
    
      def show
        @user = User.find(params[:id])
        unless @user.viewable_by?(current_user)
          raise YourApp::AuthorizationError
        end
        # ...
      end
    
      def edit
        @user = User.find(params[:id])
        unless @user.updatable_by?(current_user)
          raise YourApp::AuthorizationError
        end
        # ...
      end
    
      def update
        @user = User.find(params[:id])
        unless @user.updatable_by?(current_user)
          raise YourApp::AuthorizationError
        end
        # ...
      end
    
      def destroy
        @user = User.find(params[:id])
        unless @user.destroyable_by?(current_user)
          raise YourApp::AuthorizationError
        end
        # ...
      end
    end
    
  4. Rescue AuthorizationError in ApplicationController:

    # app/controllers/application_controller.rb
    class ApplicationController < ActionController::Base
    
      # ...
      rescue_from 'YourApp::AuthorizationError' do
        redirect_to(
          request.env["HTTP_REFERER"] || root_path,
          :alert => "You are not authorized for this action."
        )
      end
      # ...
    
    end
    
  5. Define a custom error in initializers

    # config/initializers/custom_errors.rb
    module YourApp
      class AuthorizationError < StandardError; end
    end