Pundit and controller based authorization
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).