Rails ActiveRecord 助手查找方法不急于加载关联

Vad*_*dim 5 activerecord ruby-on-rails eager-loading ruby-on-rails-3

我有以下模型:Game 和 Pick。Game 和 Pick 之间存在一对多的关联。还有第三个模型叫做 Player,一个 Player 有很多 Picks。

Player 类中有一个方法可以为给定的游戏找到一个选择,或者如果它不存在则创建一个新的。

class Player < ActiveRecord::Base
  has_many :picks

  def pick_for_game(game)
    game_id = game.instance_of?(Game) ? game.id : game
    picks.find_or_initialize_by_game_id(game_id)
  end
end
Run Code Online (Sandbox Code Playgroud)

我想急切地为每个选择加载游戏。但是,如果我这样做

picks.find_or_initialize_by_game_id(game_id, :include => :game)
Run Code Online (Sandbox Code Playgroud)

它首先在此查询运行时获取选择(该方法运行多次),然后在访问每个选择时获取游戏。如果我将 default_scope 添加到 Pick 类

class Pick < ActiveRecord::Base
  belongs_to :game
  belongs_to :player
  default_scope :include => :game
end
Run Code Online (Sandbox Code Playgroud)

它仍然为每个选择生成 2 个选择语句,但现在它在选择后立即加载游戏,但它仍然没有像我期望的那样进行连接。

Pick Load (0.2ms)  SELECT "picks".* FROM "picks" WHERE "picks"."game_id" = 1 AND ("picks".player_id = 1) LIMIT 1
Game Load (0.4ms)  SELECT "games".* FROM "games" WHERE ("games"."id" = 1)
Run Code Online (Sandbox Code Playgroud)

And*_*all 4

首先,find不支持将includeorjoin作为参数。(正如 mipsy 所说,支持 find 是没有意义的include,因为它的查询数量与稍后加载它的查询数量相同。)

其次,include急切地加载关联,所以像

Person.includes(:company)
Run Code Online (Sandbox Code Playgroud)

大致相当于做:

Person.all.each { |person| Company.find(person.company_id) }
Run Code Online (Sandbox Code Playgroud)

我说大致相当于是因为前者有O(1)(实际上是两个)查询,而后者是O(n)查询,其中n是人数。

然而,联接只是一个查询,但联接的缺点是您不能总是使用检索到的数据来更新模型。要加入,你可以这样做:

Person.join(:companies)
Run Code Online (Sandbox Code Playgroud)

您可以在 Rails Guide 中阅读有关连接表的更多信息。

总而言之,加入并不是急切加载,因为它不是加载关联,而是一次将两条数据加载在一起。我意识到两者之间有一条奇怪的细线,但急切加载是抢先获取其他数据,但您稍后不会通过联接获取该数据,或者您已经在原始查询中获取了它!希望这是有道理的。