An application defines a pundit user according to its context of shop
def pundit_user
CurrentContext.new(current_user, @shop)
end
In practice, the following policy for Contact class
def initialize(user, contact)
@user = user
@contact = contact
end
def create?
user && (user.admin? || user.editor? || (user.id == contact.user_id))
end
does not work as the user attibutes cannot be accessed.
The following error is returned for the admin
attribute of user, nor, upon removing the first two conditions, does not access the user's id.
NoMethodError: undefined method `id' for #<CurrentContext:0x0000000114537220
@user=#<User id: 830861402, email: "[email protected]", name_last: "string", name_first: "string", admin: false, editor: false [...] dconne: nil>,
@shop=#<Shop id: 1039309252, name: "[...]
An attempt to alter the initialization invoking the current context
def initialize(current_context, contact)
@user = user
@shop = shop
@contact = contact
end
fails where @shop is not recognized NameError: undefined local variable or method
shop' for #<ContactPolicy:`
How can the @user and @shop values be accessed to establish a working policy?
CodePudding user response:
You have to set up CurrentContext
class so you can use it inside the policy classes:
class CurrentContext # <= FIXME: the name is not very descriptive
# maybe `AuthorizationContext`
# NOTE: create reader methods to
# get @user and @shop variables outside of this class.
attr_reader :user, :shop
# NOTE: to make a clear distinction here. `initialize` arguments
# just hold the values that you pass to `new` method.
def initialize(user_argument, shop_argument)
@user = user_argument
@shop = shop_argument
end
end
pundit_user
method is what Pundit uses to initialize a policy class:
def pundit_user
CurrentContext.new(current_user, @shop)
end
# authorize(record)
# |
# `->Pundit.authorize(pundit_user, record, query, policy_class: policy_class, cache: policies)
# |
# `->Pundit.policy(pundit_user, record)
# |
# `->ContactPolicy.new(pundit_user, record)
Inside the policy class, just use CurrentContext
as any other object:
# @contact or Contact-.
# |
# pundit_user--. |
# v v
def initialize(user, contact)
# NOTE: `user` and `contact` are just local variables
# actual names are irrelevant.
# NOTE: `user` is an instance of `CurrentContext`
@user = user.user # get the user
@shop = user.shop # get the shop
@contact = contact
end
To make it obvious what we should get in the initializer, just rename the argument:
def initialize(user_context, contact)
@user = user_context.user
@shop = user_context.shop
@contact = contact
end