3

所以我决定尝试权威的用户授权解决方案。我想知道如何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

我想在 Projects#index 页面上显示“新项目”链接,但我在此视图中没有可用的 @project 实例变量,出现错误:

Pundit::NotDefinedError 项目#index

找不到政策 NilClassPolicy

显然出现错误是因为我确实传递@project了 nil 的实例变量,因此有一个 NilClass 显然我不需要授权。

我发现了 2 个解决此问题的方法,使其正常运行,但它们似乎都不合适:

  1. 利用视图中现有的@projects 变量,即:policy(@projects[0])
  2. 向定义此实例变量的 projects#index 控制器操作添加一行,例如。@project = Project.new(或直接在类似于上面的视图中policy(Project.new):)

第一个解决方案将导致 @projects 数组中的相同错误为空,而第二个解决方案会创建冗余实例变量。所有策略助手需要知道的是我想在哪个类上强制执行授权逻辑。

关于实现它的正确方法的任何建议?

4

2 回答 2

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.

于 2013-08-24T17:33:17.573 回答
3

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

policy方法使用此 find 方法来识别您的对象的策略类。

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类中定义一个方法调用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 方法会将“Policy”附加到 make 的末尾"ProjectPolicy"。如果是其他对象,则使用对象的名称,这也适用于大多数情况。

或者,您可以传递任何原型对象,该对象代表您想要策略的对象类型。这意味着您可以使用Policy.new创建一个,或者您可以从@policies阵列中获取一个。正如你所说,这些选项有缺点。

有很多可供您选择的选项。没有特别“正确”的方法。我更喜欢在这样的情况下传递类,因为它最符合语义。

于 2013-08-29T13:51:19.763 回答