在Pundit中使用策略助手,没有可用的实例变量

时间:2013-08-24 16:47:52

标签: ruby-on-rails ruby ruby-on-rails-3 authorization pundit

所以我决定尝试pundit用户授权解决方案。我想知道如何在视图中使用policy帮助器,其中实例变量可能为nil,如下面的简单情况:

应用程序/视图/项目/ index.html.slim

h1 Projects
(...)
- if policy(@project).create?
  = link_to 'New Project', new_project_path

应用程序/控制器/ projects_controller.rb

(...)
def index
  @projects = Project.all
end
(...)

应用程序/策略/ project_policy.rb

class ProjectPolicy < Struct.new(:user, :project)
  def create?
    user.has_role? :admin
  end

我想展示一个&#34;新项目&#34;在Projects#index页面上链接,但是我在这个视图中没有@project实例变量,收到错误:

  

项目中的Pundit :: NotDefinedError #index

     

无法找到

的政策NilClassPolicy

错误显然是因为我传递的@project实例变量是nil,因此有一个NilClass,显然我没有必要授权。

我找到了2个解决此问题的方法,使其正确运行,但似乎没有一个是合适的:

  1. 在视图中使用现有的@projects变量,即:policy(@projects[0])
  2. 为项目添加一行#index controller action定义此实例变量,例如。 @project = Project.new(或直接与上面类似的视图:policy(Project.new)
  3. 第一个解决方案将导致@projects数组中的相同错误为空,而第二个解决方案将创建冗余实例变量。所有策略帮助程序需要知道的是我要在哪个类上强制执行授权逻辑。

    有关正确实现目标的任何建议吗?

2 个答案:

答案 0 :(得分:5)

你提出的第二个解决方案是我的工作。

- if policy(@project).create?
  = link_to 'New Project', new_project_path

此处针对新记录的策略检查应使用Project.new (无论是否已分配给实例变量)

- if policy(Project.new).create?
  = link_to 'New Project', new_project_path

无论如何,必须将Project的实例传递给policy帮助程序,以便Pundit派生策略类ProjectPolicy来执行create?检查查找。当您传递nil时,这就是您看到Pundit派生NilClassPolicy的原因。

答案 1 :(得分:3)

通常,单个实例与该类中的所有其他实例具有相同的策略 - 这实际上是Pundit正常工作的方式。在这种情况下,您真的不关心属于特定实例的策略;相反,你正在寻找一类物体的政策。

policy方法使用this find method来标识对象的策略类。

def find
  if object.respond_to?(:policy_class)
    object.policy_class
  elsif object.class.respond_to?(:policy_class)
    object.class.policy_class
  else
    klass = if object.respond_to?(:model_name)
      object.model_name
    elsif object.class.respond_to?(:model_name)
      object.class.model_name
    elsif object.is_a?(Class)
      object
    else
      object.class
    end
    "#{klass}Policy"
  end
end

您可以将任何对象传递给策略方法。由于类是对象,因此可以传入类。在find方法中,第一个条件检查您传递的对象是否响应policy_class,因此您可以在Project中定义方法} class policy_class并让该方法返回ProjectPolicy类。

class Project < ActiveRecord::Base
  def self.policy_class
    ProjectPolicy
  end
end

如果您没有定义policy_class方法,那么前两个条件将失败,然后find方法将尝试构造策略类名称。它首先查看object.model_name。如果你在Rails中并且你的模型扩展了ActiveModel :: Naming(ActiveRecord :: Base那么),它就会响应model_name,如果你正在做典型的Rails-ey的东西,那个名称正是你想要的:它将返回"Project"。然后,find方法将附加&#34; Policy&#34;到最后要做"ProjectPolicy"。如果它是某个其他对象,则使用该对象的名称,这也适用于大多数情况。

或者,您可以传递任何表示您希望策略对象类型的原型对象。这意味着您可以使用Policy.new创建一个,或者您可以从@policies数组中获取一个。正如你所说,这些选择有缺点。

您可以选择很多选项。没有特别的&#34;正确的&#34;办法。我喜欢在这样的情况下传递类,因为它具有最大的语义意义。