Pundit和ActiveRecord合并的联接

时间:2018-07-11 10:24:37

标签: ruby-on-rails ruby activerecord ruby-on-rails-5 pundit

我正在使用Pundit gem来限定应用程序的角色范围,发现合并ActiveRecord查询时遇到了一些困难。我正在使用Rails 5.1.4。

看到我有三个模型,可以说“课堂”,“学生”和“考试”:

  • 教室has_many:students和has_many:exams,通过::students
  • 学生归属于::教室和has_many:考试
  • 考试归属于::学生

每个人都有一个策略范围,其中包含多个查询,我希望将它们合并在一起,以确保用户可以访问学生范围(也属于“课堂”范围)中的考试模型。

Pundit通过将任何ActiveRecord关系从控制器传递到作用域变量内的Scope类来做到这一点。

请记住,我的范围类如下所示:

class ClassroomPolicy::Scope
  def resolve
    if user.reviewer?
      # A reviewer can only see specific classes
      scope.where(type: :exam).where(reviewer_id: user.id)
    end
  end
end

class StudentPolicy::Scope
  def resolve
    if user.reviewer?
      # A reviewer can only see students from classes he's allowed in
      scope.joins(:classroom).merge(policy_scope(Classroom))
    end
  end
end

class ExamPolicy::Scope
  def resolve
    if user.reviewer?
      # A reviewer can only see/grade exams from students he supervised
      scope.joins(:student).merge(policy_scope(Student))
    end
  end
end

一切正常,如果我做policy_scope(Exam),我的确会从审阅者教室内的学生那里获得所有考试。

传递诸如policy_scope(Classroom.find(params[:classroom_id]).exams)之类的已加入查询时,会出现此问题。
ActiveRecord有效地生成了一个无法使用的奇怪查询,因为a_classroom.exams和我们的范围都对:student进行了连接。

此外,将joins从我们的考试范围中删除,给我们scope.merge(policy_scope(Student)),使我们的通话policy_scope(Classroom.find(params[:classroom_id]).exams)在打破policy_scope(Exam)的同时工作。

是否有一种解决方法,以便两个用例都能正常工作?
还是我对Pundit的态度是错误的?
这是ActiveRecord的限制吗?
任何帮助,将不胜感激!

1 个答案:

答案 0 :(得分:2)

policy_scope适用于Controller。例如参考https://github.com/varvet/pundit#scopes部分中给出的示例,您应该发现该方法已被证明可以从控制器或视图中使用。

但是,根据您的代码,您自己尝试在自己的作用域类(即扩展policy_scope类的类)中使用ApplicationPolicy::Scope。我想我理解这样做的意图是重用已经定义的作用域。但是我认为这是一种错误的思考方式。

一旦进入YourScopeClass#resolve方法,您应该像在Rails应用程序中的其他地方一样使用常规查询机制。

我对您模型中的字段没有完全的专业知识。但是,根据您在resolve方法中为评论者添加的评论,您应该执行以下操作:

class StudentPolicy::Scope
  def resolve
    if user.reviewer?
      # A reviewer can only see students from classes he's allowed in
      arel = scope.joins(:classroom)
      arel = arel.where(classrooms: { type: : exam, reviewer_id: user.id })
      arel
    end
  end
end

然后从policy_scope(Student)之类的控制器中使用它

类似地

class ExamPolicy::Scope
  def resolve
    if user.reviewer?
      # A reviewer can only see/grade exams from students he supervised
      arel = arel.joins(student: [ :classroom ])
      arel = arel.where(classrooms: { type: : exam, reviewer_id: user.id })
      arel
    end
  end
end

然后从policy_scope(Exam)之类的控制器中使用它

如果您想重用某些查询,请尝试创建一些通用方法,这些方法可以通过Pundit Policy Scope代表的查询传递。

例如

class MyClass
  class << self
    def accessible_students(arel:, user_id:)
      arel.where(classrooms: { type: : exam, reviewer_id: user_id })
    end
  end
end

class StudentPolicy::Scope
  def resolve
    if user.reviewer?
      # A reviewer can only see students from classes he's allowed in

      arel = scope.joins(:classroom)
      arel = MyClass.accessible_students(arel: arel, user_id: user.id)
      arel
    end
  end
end

class ExamPolicy::Scope
  def resolve
    if user.reviewer?
      # A reviewer can only see/grade exams from students he supervised
      arel = arel.joins(student: [ :classroom ])
      arel = MyClass.accessible_students(arel: arel, user_id: user.id)
      arel
    end
  end
end

希望您对此有帮助。