在CanCanCan

时间:2016-01-11 20:59:24

标签: ruby-on-rails eager-loading cancancan

所以我和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,以及项目)。项目索引页面上还添加了与团队的快速链接。

1 个答案:

答案 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