PunditCan

Pundit with cancan style load_and_authorize functionality.

Usage

Include PunditCan::LoadAndAuthorize into ApplicationController or in each controller.

Call load_resource in the controller to load and authorize the resource.

class UsersController < ApplicationController
  load_resource
end

This will load @user from User using the UserPolicy to authorize and scope the loading.

Advanced usage

There are options to customize the loaded instance_name, model, and policy classes.

Parent / nested

This is an example of loading User and Posts, where posts are scoped through the user.

class PostsController < ApplicationController
  load_resource model_class: User, parent: true
  load_resource through: :user

  ...
end

The :through option tells load_resource to pass the parent's association as the scope through the policy. For example, if @user was loaded by the first call, the second call will pass @user.posts to PostPolicy::Scope instead of Post.all. This allows the policy scope to work with the already-authorized parent.

That will load @user from the UserPolicy into a User class, using :user_id to find the user. And it will load @post or @posts using the PostPolicy with the :id param.

If there is no parent instance variable set (e.g., a non-nested route), it will fall back to the default behavior of scoping with the model class.

When the association name doesn't match the model name, use the :association option:

class ArticlesController < ApplicationController
  load_resource model_class: User, parent: true
  load_resource model_class: Article, through: :user, association: :articles

  # This passes @user.articles instead of @user.posts to the ArticlePolicy::Scope
end

This is useful when you have associations like has_many :published_posts, class_name: "Post" or other cases where the association method name differs from the model class name.

When the parent relationship attribute in the model uses a different name than the :through option, use the :parent_key option:

class ArticlesController < ApplicationController
  load_resource model_class: User, parent: true
  load_resource model_class: Article, through: :user, parent_key: :admin

  # This builds Article.new(admin: @user) instead of Article.new(user: @user)
end

This is useful when the model's foreign key or association attribute name differs from the parent's instance name. For example, when belongs_to :admin, class_name: "User" or belongs_to :author, class_name: "User" is used instead of a standard belongs_to :user.

Customized loading

You can customize the loading for cases when the model, controller, and policies don't match up name-wise.

class MisMatchedController < ApplicationController
  load_resource instance_name: :special_user,
    model_class: User,
    policy_class: SpecialUserPolicy,
    policy_scope_class: SpecialUserPolicy::Scope

  ...

  # Pundit method to override the model param key
  def pundit_params_for(record)
    params.require(:special_user)
  end
end

This will set @special_user with the User class, using the SpecialUserPolicy and SpecialUserPolicy::Scope classes to authorize and scope the loading.

Skiping checks

By default, verify_authorized and verify_policy_scoped after actions are setup. If you need to skip those for an action, there are skip_authorized_check and skip_scoped_check methods to skip the verify actions for the given actions.

class SkipsController < ApplicationController
  skip_authorized_check :index, :show
  skip_scoped_check :index, :show

  ...
end

Installation

Add this line to your application's Gemfile:

gem "pundit_can"

And then execute:

$ bundle

Or install it yourself as:

$ gem install pundit_can

Contributing

Contribution directions go here.

License

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