I'm using Pundit for authorization and I want to make use of its scoping mechanisms for multi-tenancy (driven by hostname).
I've been doing this manually to date by virtue of:
class ApplicationController < ActionController::Base
# Returns a single Client record
def current_client
@current_client ||= Client.by_host(request.host)
end
end
And then in my controllers doing things like:
class PostsController < ApplicationController
def index
@posts = current_client.posts
end
end
Pretty standard fare, really.
I like the simplicity of Pundit's verify_policy_scoped
filter for ensuring absolutely every action has been scoped to the correct Client
. To me, it really is worthy of a 500 error if scoping has not been officially performed.
Given a Pundit policy scope:
class PostPolicy < ApplicationPolicy
class Scope < Scope
def resolve
# have access to #scope => Post class
# have access to #user => User object or nil
end
end
end
Now, Pundit seems to want me to filter Post
s by user, e.g.:
def resolve
scope.where(user_id: user.id)
end
However, in this scenario I actually want to filter by current_client.posts
as the default case. I'm not sure how to use Pundit scopes in this situation but my feeling is it needs to look something like:
def resolve
current_client.posts
end
But current_client
is naturally not going to be available in the Pundit scope.
One solution could be to pass current_client.posts
to policy_scope
:
def index
@posts = policy_scope(current_client.posts)
end
But I feel this decentralizes my tenancy scoping destroys the purpose of using Pundit for this task.
Any ideas? Or am I driving Pundit beyond what it was designed for?
The most "Pundit-complient" way to deal with this problem would be to create a scope in your
Post
model:Then, you will be able to use it in your policy, where
user
is filled with thecurrent_user
from your controller:If you are returning an ActiveRecord::Relation from the scope, you can stop reading from here.
If your scope returns an array
The default
ApplicationPolicy
implement the methodshow
using awhere
: source.So if your scope does not return an AR::Relation but an array, one work-around could be to override this
show
method:Whatever your implementation is, you just need to use the
PostPolicy
from your controller the "Pundit-way":