我正在使用Pundit gem来限定应用程序的角色范围,发现合并ActiveRecord查询时遇到了一些困难。我正在使用Rails 5.1.4。
看到我有三个模型,可以说“课堂”,“学生”和“考试”:
每个人都有一个策略范围,其中包含多个查询,我希望将它们合并在一起,以确保用户可以访问学生范围(也属于“课堂”范围)中的考试模型。
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的限制吗?
任何帮助,将不胜感激!
答案 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
希望您对此有帮助。