Eager Loading has_many虽然属于belongs_to关系

时间:2015-10-02 17:31:15

标签: ruby-on-rails rails-activerecord

我有一些这样的模型:

class Image < ActiveRecord::Base
  has_many :tag_groups
end

class TagGroup < ActiveRecord::Base
  belongs_to :image
  has_many :tag_group_members
  has_many :tags, through: :tag_group_members
end

class TagGroupMember < ActiveRecord::Base
  belongs_to :tag_group
  belongs_to :tag
end

class Tag
  has_many :tag_group_members
  has_many :tag_groups, through: :tag_group_members
end

我想为图像选择所有标签组及其标签。显而易见的解决方案是:

Image
  .includes(tag_groups: :tags)
  .find(params[:id])

但是,通过迭代(@image.tag_groups.map{|g| g.tags.map{|t| t.name}}),我们发现Rails正在进行大量的SQL查询:

TagGroup Load (0.8ms)  SELECT "tag_groups".* FROM "tag_groups" WHERE "tag_groups"."image_id" IN (1)
  Tag Exists (0.3ms)  SELECT  1 AS one FROM "tags" INNER JOIN "tag_group_members" ON "tags"."id" = "tag_group_members"."tag_id" WHERE "tag_group_members"."tag_group_id" = $1 LIMIT 1  [["tag_group_id", 1]]
  Tag Load (0.3ms)  SELECT "tags".* FROM "tags" INNER JOIN "tag_group_members" ON "tags"."id" = "tag_group_members"."tag_id" WHERE "tag_group_members"."tag_group_id" = $1  [["tag_group_id", 1]]
  Tag Exists (0.3ms)  SELECT  1 AS one FROM "tags" INNER JOIN "tag_group_members" ON "tags"."id" = "tag_group_members"."tag_id" WHERE "tag_group_members"."tag_group_id" = $1 LIMIT 1  [["tag_group_id", 3]]
  Tag Load (0.3ms)  SELECT "tags".* FROM "tags" INNER JOIN "tag_group_members" ON "tags"."id" = "tag_group_members"."tag_id" WHERE "tag_group_members"."tag_group_id" = $1  [["tag_group_id", 3]]
  Tag Exists (0.6ms)  SELECT  1 AS one FROM "tags" INNER JOIN "tag_group_members" ON "tags"."id" = "tag_group_members"."tag_id" WHERE "tag_group_members"."tag_group_id" = $1 LIMIT 1  [["tag_group_id", 4]]
  Tag Load (0.6ms)  SELECT "tags".* FROM "tags" INNER JOIN "tag_group_members" ON "tags"."id" = "tag_group_members"."tag_id" WHERE "tag_group_members"."tag_group_id" = $1  [["tag_group_id", 4]]

现在,似乎只需三次查询即可轻松处理:

SELECT images.* FROM images WHERE images.id = $1

然后:

SELECT tag_groups.* FROM tag_groups WHERE tag_groups.image_id = $1

最后:

SELECT tags.*, tag_groups.id AS tag_group_id FROM tags
INNER JOIN tag_group_members ON tag_group_members.tag_id = tags.id
INNER JOIN tag_groups ON tag_groups.id = tag_group_members.tag_group_id
WHERE tag_groups.image_id = $1 -- alternatively, where tag_groups.id IN (results from the previous query)

然后,您可以简单地将标记关联到内存中正确的标记组,这比查询每个标记组的数据库要快得多。

有没有办法让Rails加载这个关联而不进行那么多连接?

2 个答案:

答案 0 :(得分:1)

您甚至不需要提到的第二个查询,因为您的加入查询中已经有tag_groups.image_id

i = Image.find params[:id]
tgs = i.tag_groups.joins(:tags)

更新

有趣的是,我没有意识到joins也没有急切加载内存中的关联,似乎您需要在上面的代码中添加.includes(:tags)以防止任何其他查询正在运行。

此外,这也完成了同样的事情,但只需一步:

i = Image.joins(tag_groups: :tags).includes(tag_groups: :tags).find 1

答案 1 :(得分:0)

只需:

@image = Image.includes(tag_groups: {tag_group_members: :tags}).find(param[:id])

首先从include致电Image,然后返回ActiveRecord::Relation,之后您可以致电find

这样就可以了。