所以我和CanCanCan一起使用rails。 我有用户可以访问0个或更多项目 每个项目都有一个或多个子项目。 项目和子项目都有经理 如果您是项目的经理,则可以查看其所有子项目 如果您是子项目的经理,您还可以看到该项目(但不一定是该项目中的任何其他子项目)
class Project < ActiveRecord::Base
has_many :sub_projects
has_many :ordered_sub_projects, ->() { order('name') }
has_many :project_managers
end
class ProjectManagers < ActiveRecord::Base
belongs_to :user
belongs_to :sub_project
end
class Subproject < ActiveRecord::Base
belongs_to :project
has_many :subproject_managers
end
class SubProjectManagers < ActiveRecord::Base
belongs_to :user
belongs_to :sub_project
end
class Ability
def initialize(user)
can? :read, Project, { managers: user.id }
can? :read, Project, { sub_projects: { subproject_managers: user.id } } # To allow to see projects if you are manager of subproject
can? :read, SubProject, { subproject_managers: user.id }
can? :read, SubProject, { project: { project_managers: user.id } }
end
end
现在我想显示给定用户的所有项目,我可以这样做:
Projects.all.accessible_by(ability)
但是在我的概述页面上,我还想为所有(允许的)子项目添加快速链接(使用引导下拉列表)。 最初我在视图代码中使用了以下等价物:
Projects.all.accessible_by(ability).order(:name).each do |project|
project.sub_projects.accessible_by(ability).order(:name).each do |sub|
add_link sub.name, sub
end
end
但是这在显示许多项目时引起了巨大的N + 1问题。 [1] 所以现在我改为。
Projects.all.accessible_by(ability).
preload(:ordered_subprojects).order(:name).each do |project|
project.ordered_subprojects.each do |sub|
add_link sub.name, sub if can?(:read, sub)
end
end
哪个更快但更不干净。我怀疑可以吗?在这种情况下,方法也相对较慢。
理想情况下,可以通过加载正确的accessible_by来加载关联的has_many。不幸的是,我没有看到这样做的方法
[1]实际上情况更糟,因为每个子项目中还有一些团队,其中类似的访问语义为SubProject(每个团队有一个或多个maangers,如果你能看到团队,你可以看到SubProject,以及项目)。项目索引页面上还添加了与团队的快速链接。
答案 0 :(得分:1)
您可以使用类似于内部急切加载的技术 - 一次获取所有可访问的子项目,然后按project_id
对它们进行分组。
像这样:
subprojects_hash = Subproject.accessible_by(ability).group_by(&:project_id)
Projects.all.accessible_by(ability).each do |project|
# do smth with project
if subprojects_hash[project.id]
# and subprojects...
end
end