Home > database >  Pundit context access
Pundit context access

Time:07-12

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
  • Related