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

私はあなたが何かを逃しているとは思わない。アクティブ レコードがそれほどスマートにできるとは思えません。また、確実に実行するのは非常に難しいと思います。あなたが言うように、アソシエーションをeager-loadしたかどうかを判断する必要がありますが、メモリ内のCのコレクションをループする方が速いかどうかについても推測する必要があります(小さい場合)コレクション) またはデータベースに移動して、適切な 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 を照会するときに DB に戻らないことを常に知ることができます。

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 に答える