If we have an Order class, pundit gem will figure out to use the OrderPolicy for authorization. But what if we have multiple domains using the same item?

For example an online auction site, there will be a Seller::OrdersController and a Buyer::OrdersController. They will manage the two sides of the same order. The idea is that, buyer should be allowed to only update via Buyer::OrdersController but not Seller::OrdersController. And vice versa for seller. Obviously we would want to have two sets of policies.

However in Pundit namespaced policy require us to call authorze [:seller, item], in order for it to use the Seller::ItemPolicy. This can become repetitive.

For me, there is less room for error if we have a 1:1 relationship between controller and policy, and a controller can be assumed to use the same policy. So I patched authorize call so policy can be set on the controller level.

Solution

Put the following under ApplicationController as private methods.

  class_attribute :pundit_policy_class

  def self.set_pundit_policy_class(klass)
    self.pundit_policy_class = klass
  end

  def authorize(record, query: nil, policy_class: nil)
    query ||= params[:action].to_s + "?"

    @_pundit_policy_authorized = true

    if policy_class
      policy = policy_class.new(pundit_user, record)
    elsif self.pundit_policy_class
      policy = self.pundit_policy_class.new(pundit_user, record)
    else
      policy = policy(record)
    end

    unless policy.public_send(query)
      raise NotAuthorizedError, query: query, record: record, policy: policy
    end

    record
  end

In your controller, you can then do this to set policy:

class Seller::OrdersController < ApplicationController
  set_pundit_policy_class Seller::OrderPolicy

This means all actions under this controller will use Seller::OrderPolicy by default when you call authorize.

If we want to override this, we can also pass in policy_class in authorize:

   authorize @order, policy_class: FooPolicy

Note that I made some changes to authorize’s method signature: query is a keyword argument now. It also returns the record object (as planned for its 1.2 release).