使用rails active_record'包含'根据包含的内容更改has_many属性访问器的结果

时间:2012-06-13 17:01:49

标签: ruby-on-rails activerecord

我有ProjectUser模型,它们通过Permission模型与has_many through,多态关联连接。 Permission表还跟踪挂起状态。相关设置如下:

class Project < ActiveRecord::Base
  has_many :permissions, as: :permissionable
  has_many :contributors, through: :permissions, source: :user
  has_many :pending_contributors, through: :permissions, source: :user, conditions: '"permissions"."accepted" = false'
  ...
end

class Permission < ActiveRecord::Base
   belongs_to :user
   belongs_to :permissionable, polymorphic: true
   ...
end

class User < ActiveRecord::Base
  has_many :permissions
  has_many :projects, through: :permissions, source: :permissionable, source_type:'Project'
  ...
end

我正在尝试访问项目的贡献者和pending_contributors列表,而不会在渲染所有项目时导致N + 1查询问题。问题是我无法使用includes(:contributors, :pending_contributors),因为首先提到的两个包含中的任何一个是唯一有效的。

例如在我的rails控制台中:

u = User.find(2)
u.projects.includes(:contributors, :pending_contributors).each do 
  |p| puts p.contributors.map(&:id).inspect
end   

我在rails控制台中的结果是:

User Load (0.7ms)  SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1  [["id", 2]]
Project Load (1.3ms)  SELECT "projects".* FROM "projects" INNER JOIN "permissions" ON "projects"."id" = "permissions"."permissionable_id" WHERE "permissions"."user_id" = 2 AND "permissions"."permissionable_type" = 'Project'
Permission Load (1.1ms)  SELECT "permissions".* FROM "permissions" WHERE "permissions"."permissionable_type" = 'Project' AND "permissions"."permissionable_id" IN (64, 56, 54, 53, 51, 50, 61, 62, 63, 57, 65, 66, 48, 49, 67, 68, 69, 70, 71, 60, 72, 16, 14, 11)
User Load (0.7ms)  SELECT "users".* FROM "users" WHERE "users"."id" IN (2, 18, 20, 41, 40, 3, 17, 34, 37, 39, 43, 19, 22, 42)

前三个结果是:

[2, 18, 20, 2]
[2]
[41, 2, 40, 18]

现在按如下方式交换包含的顺序:

u = User.find(2)
u.projects.includes(:pending_contributors, :contributors).each do 
  |p| puts p.contributors.map(&:id).inspect
end

突然结果发生了变化(产生了我预期的结果) - 注意权限加载行,它现在将:pending_contributors条件添加到该查询中:

User Load (0.8ms)  SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1  [["id", 2]]
Project Load (1.1ms)  SELECT "projects".* FROM "projects" INNER JOIN "permissions" ON "projects"."id" = "permissions"."permissionable_id" WHERE "permissions"."user_id" = 2 AND "permissions"."permissionable_type" = 'Project'
Permission Load (1.4ms)  SELECT "permissions".* FROM "permissions" WHERE "permissions"."permissionable_type" = 'Project' AND "permissions"."permissionable_id" IN (64, 56, 54, 53, 51, 50, 61, 62, 63, 57, 65, 66, 48, 49, 67, 68, 69, 70, 71, 60, 72, 16, 14, 11) AND ("permissions"."accepted" = false)
User Load (0.8ms)  SELECT "users".* FROM "users" WHERE "users"."id" IN (18, 19, 40, 3, 22, 43, 42, 41, 2, 20)

结果是:

[20]
[]
[41, 40, 18]

如果不是访问pending_contributors,而是访问贡献者,我会反过来得到同样的问题。我很确定这是因为pending_contributors和贡献者都在使用相同的源,但我不确定为什么这是一个问题。

有没有办法为每个项目呈现所有项目和访问贡献者和pending_contributors而不会导致N + 1查询?

注意:我在Rails 3.2上。

1 个答案:

答案 0 :(得分:0)

通过尝试将同一个表中的两组重叠行加载到不同的关联中,您看起来很混淆ActiveRecord。

由于pending_contributors中包含contributors关联,我不会将其作为单独的关联,特别是如果你要像这样并排使用它们。

相反,我会将pending_contributors定义为贡献者的过滤器:

class Project < ActiveRecord::Base
  has_many :permissions, as: :permissionable
  has_many :contributors, through: :permissions, source: :user
  def pending_contributors
    permissions.include(:user).select do |p|
      not p.accepted
    end.map do |p|
      p.user
    end
  end
  ...
end

这并不是完全有效的,因为它意味着每次访问pending_contributors时,它都会从数据库中引入所有contributors,但我认为在这种情况下,这是一个非常合理的权衡。

替代方案是ActiveRecord中的一些深度潜水,或者在模型上同时使用关联和过滤方法,这看起来有点混乱。