Implementing Namespaced Pundit Policies within Administrate

Recently, I had to implement several changes to the authorization logic of a Rails application, but the process did not go as smoothly as I had anticipated.

The app is an internal project management tool that has an admin panel built using the Administrate gem. Initially, only the admin role had access to that view, but as requirements changed, new user roles had to be able to use the admin panel, yet not be able to interact with all the resources. To make things more complicated, I couldn't rely on existing Pundit policies, since the scope of the resources allowed inside the admin panel was more limited.

I started by reading the documentation for Administrate and found out that you could define a policy_namespace method inside the Admin::ProjectsController. After creating the method and setting the namespace to be [:admin], Pundit was not loading the newly created /policies/admin/project_policy.rb. It was still relying on the main /policies/project_policy.rb.

After some debugging and looking for any errors on my part, I discovered through their issues that a quick workaround involves making the following modifications to Admin::ApplicationController:

  • Remove the call to include Administrate::Punditize

  • Include Pundit

  • Override Pundit's methods, adding the :admin namespace

Here's what I've ended up with:

# app/controllers/admin/application_controller.rb

module Admin
  class ApplicationController < Administrate::ApplicationController
    include Pundit

    before_action :authenticate_admin

    ALLOWED_ROLES = %w[admin editor].freeze

    def authenticate_admin
      return true if ALLOWED_ROLES.include? current_user.role
      redirect_to root_path, alert: 'Unauthorized!'
    end

    def policy_scope(scope)
      super([:admin, scope])
    end

    def authorize(record, query = nil)
      super([:admin, record], query)
    end

    def scoped_resource
      policy_scope super
    end

    def authorize_resource(resource)
      authorize resource
    end

    def show_action?(action, resource)
      Pundit.policy!(pundit_user, [:admin, resource]).send("#{action}?".to_sym)
    end
  end
end

And the corresponding policy:

# app/policies/admin/project_policy.rb

module Admin
  class ProjectPolicy < ApplicationPolicy
    def index?
      user.admin?
    end

    def show?
      index?
    end

    def create?
      show?
    end

    def edit?
      create?
    end

    def update?
      edit?
    end

    def destroy?
      update?
    end
  end
end

This is a quick workaround, and I will continue looking for an optimal solution, but for now it effectively gets the job done.

I hope this is helpful to anyone encountering the same issue.