A有很多B,B有很多Cs。 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">]
>
- 但不是我想稍后对属于B实例的C进行where()
次搜索的情况:
> 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
方法将在执行过滤之前加载所有C for B,而where
方法将生成SQL以获取较小的数据集。
我不相信这一点很重要,但是如果我有一些关于急切装载的东西,我很乐意听到它。
答案 0 :(得分:1)
我认为你没有遗漏任何东西。我不相信积极的记录可以做任何聪明的事情 - 而且我认为很难做到可靠。就像你说的那样,它必须确定你是否已经急切地加载了这个关联,但它还必须猜测是否更快地循环内存中的Cs集合(如果它是一个小的收集)或是否更快进入数据库以一次性获得所有适当的Cs(如果它是一个非常大的集合)。
在你的情况下,最好的办法可能就是将默认范围设置为始终预加载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)