Ruby on rails 在Pundit中使用没有实例变量的策略帮助程序

Ruby on rails 在Pundit中使用没有实例变量的策略帮助程序,ruby-on-rails,ruby,ruby-on-rails-3,authorization,pundit,Ruby On Rails,Ruby,Ruby On Rails 3,Authorization,Pundit,所以我决定提供一个尝试用户授权的解决方案。我想知道在实例变量可能为nil的情况下如何使用策略帮助器,如下所示: app/views/projects/index.html.slim h1 Projects (...) - if policy(@project).create? = link_to 'New Project', new_project_path app/controllers/projects\u controller.rb (...) def index @projec

所以我决定提供一个尝试用户授权的解决方案。我想知道在实例变量可能为nil的情况下如何使用
策略
帮助器,如下所示:

app/views/projects/index.html.slim

h1 Projects
(...)
- if policy(@project).create?
  = link_to 'New Project', new_project_path
app/controllers/projects\u controller.rb

(...)
def index
  @projects = Project.all
end
(...)
app/policies/project_policy.rb

class ProjectPolicy < Struct.new(:user, :project)
  def create?
    user.has_role? :admin
  end
class ProjectPolicy
我想在Projects#index页面上显示一个“New Project”链接,但我在此视图中没有@Project实例变量,因此出现错误:

专家::项目中未定义错误#索引

找不到的策略NilClassPolicy

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

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

  • 使用视图中现有的@projects变量,即:
    policy(@projects[0])
  • 在定义此实例变量的项目#索引控制器操作中添加一行,例如
    @project=project.new
    (或直接在类似于上面的视图中:
    策略(project.new)
  • 第一个解决方案将导致@projects数组中的相同错误为空,而第二个解决方案将创建冗余实例变量。policy helper需要知道的是我希望在哪个类上强制执行授权逻辑


    有没有关于实现这一目标的正确方法的建议?

    你提出的第二个解决方案就是我所做的

    - if policy(@project).create?
      = link_to 'New Project', new_project_path
    
    此处新记录的策略检查应使用
    Project.new
    (无论它是否分配给实例变量)


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

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

    policy
    方法用于标识对象的策略类

    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\u类
    ,因此您可以在
    项目
    类中定义一个名为
    policy\u类
    的方法,并让该方法返回您的
    ProjectPolicy

    class Project < ActiveRecord::Base
      def self.policy_class
        ProjectPolicy
      end
    end
    
    class项目
    如果不定义policy_类方法,那么前两个条件将失败,find方法将尝试构造策略类名。它首先查看object.model\u名称。如果您在Rails中,并且您的模型扩展了ActiveModel::Naming(ActiveRecord::Base就是这样做的),那么它将已经响应
    model\u name
    ,如果您正在执行典型的Rails ey操作,那么这个名称正是您想要的:它将返回
    “Project”
    。然后,find方法将在其末尾附加“Policy”,以使
    成为“ProjectPolicy”
    。如果是其他对象,则使用该对象的名称,这在大多数情况下也适用

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

    你有很多选择。没有特别“正确”的方法。我更喜欢在这种情况下传递这个类,因为它最有语义意义

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