3

A有很多B,B有很多C。C 有一个名为 的属性thing

class A < ActiveRecord::Base
  has_many :bs
end
class B < ActiveRecord::Base
  belongs_to :a
  has_many :cs
end
class C < ActiveRecord::Base
  belongs_to :b
  attr_accessible :thing
end

我想查询属于 A 的所有 B,并急切地加载属于所述 B 的 C:

> a = A.first
  A Load (0.2ms)  SELECT "as".* FROM "as" LIMIT 1
 => #<A id: 1, created_at: "2012-08-21 09:25:18", updated_at: "2012-08-21 09:25:18"> 
> bs = a.bs.includes(:cs)
  B Load (0.2ms)  SELECT "bs".* FROM "bs" WHERE "bs"."a_id" = 1
  C Load (0.1ms)  SELECT "cs".* FROM "cs" WHERE "cs"."b_id" IN (1)
 => [#<B id: 1, a_id: 1, created_at: "2012-08-21 09:25:22", updated_at: "2012-08-21 09:25:22", thing: nil>] 
> 

这很好用:

> bs[0]
 => #<B id: 1, a_id: 1, created_at: "2012-08-21 09:25:22", updated_at: "2012-08-21 09:25:22", thing: nil> 
> bs[0].cs
 => [#<C id: 1, b_id: 1, thing: 2, created_at: "2012-08-21 09:29:31", updated_at: "2012-08-21 09:29:31">] 
> 

——但不是在我想稍后where()对属于 B 实例的 C 执行搜索的情况下:

> bs[0].cs.where(:thing => 1)
  C Load (0.2ms)  SELECT "cs".* FROM "cs" WHERE "cs"."b_id" = 1 AND "cs"."thing" = 1
 => [] 
> bs[0].cs.where(:thing => 2)
  C Load (0.2ms)  SELECT "cs".* FROM "cs" WHERE "cs"."b_id" = 1 AND "cs"."thing" = 2
 => [#<C id: 1, b_id: 1, thing: 2, created_at: "2012-08-21 09:29:31", updated_at: "2012-08-21 09:29:31">] 
> 

请注意,尽管我们有可用信息,但仍会重新发出查询。

当然,我可以使用Enumerable#select

> bs[0].cs.select {|c| c.thing == 2}
 => [#<C id: 1, b_id: 1, thing: 2, created_at: "2012-08-21 09:29:31", updated_at: "2012-08-21 09:29:31">] 
>

这避免了重新查询,但我有点希望 Rails 本身可以做类似的事情。

真正的缺点是我想在我们不知道关联是否已被热切加载的情况下使用此代码。如果没有,则该select方法将在执行过滤之前为 B 加载所有 C,而该where方法将生成 SQL 以获取较小的数据集。

我根本不相信这很重要,但是如果我在急切加载方面缺少什么,我很想听听。

4

1 回答 1

1

我不认为你缺少任何东西。我不相信主动记录可以做任何聪明的事情——而且我认为要可靠地做到这一点非常困难。就像你说的那样,它必须确定你是否已经预先加载了关联,但它还必须猜测循环遍历 Cs 的内存集合是否更快(如果它很小集合)或者去数据库一次获取所有适当的 C 是否会更快(如果它是一个非常大的集合)。

在您的情况下,最好的办法可能是将默认范围设置为始终预加载 cs,甚至可能编写自己的奇特方法来逐个获取它们。可能是这样的:

class B < ActiveRecord::Base
  belongs_to :a
  has_many :cs
  default_scope includes(:cs)

  def cs_by_thing(thing)
    cs.select{|c|c.thing == thing}
  end
end

然后你总是可以知道在查询你的 cs 时你永远不会回到数据库:

a = A.first
[db access]
a.bs.first
[db access]
a.bs.first.cs
a.bs.first.cs_by_thing(1)
a.bs.first.cs_by_thing(2)
于 2012-09-21T18:02:24.543 回答