【问题标题】:Rails pre-loading association with multiple foreign keysRails 预加载关联多个外键
【发布时间】:2018-01-26 19:28:05
【问题描述】:

假设我有以下两个模型,分别由以下两个连接模型连接:

class Game
  has_many :game_plays
  has_many :game_views
end

class Person
  has_many :game_plays
  has_many :game_views
end

# Games that a Person has played
class GamePlay
  belongs_to :game
  belongs_to :person
end

# Games that a Person has viewed
class GameView
  belongs_to :game
  belongs_to :person
end

给定一个特定的GamePlay,我想为相同的Person-Game 组合获得GameView,例如:

game_play = GamePlay.first
game_view = GameView.find_by(game_id: game_play.game_id, person_id: game_play.person_id)

我还需要预先加载该关联。


我很想在GamePlayGameView 之间建立一个关联,但到目前为止我没有尝试过任何方法。

尝试 1

class GamePlay
  belongs_to :game
  belongs_to :person

  has_one :game_view, -> (gp) { where(game_id: gp.game_id) }, foreign_key: :person_id, primary_key: :person_id
end

这行得通,但我不能include这个:

GamePlay.includes(:game_view).first
# => ArgumentError: The association scope 'game_view' is instance dependent (the scope block takes an argument). Preloading instance dependent scopes is not supported.

尝试 2

class GamePlay
  belongs_to :game
  belongs_to :person

  def game_view
    GameView.find_by(game_id: game_id, person_id: person_id)
  end
end

这显然有效,但我不能包含它,因为它没有被定义为关联。

有什么想法吗?谢谢!

Rails 5.0.0
postgres 9.6.2

【问题讨论】:

    标签: ruby-on-rails postgresql activerecord


    【解决方案1】:

    怎么样:

    class GamePlay < ApplicationRecord
      belongs_to :game
      belongs_to :person
      has_one :game_view, through: :person, source: :game_views
    end
    
    irb(main):002:0> GamePlay.includes(:game_view).find(2)
      GamePlay Load (0.2ms)  SELECT  "game_plays".* FROM "game_plays" WHERE "game_plays"."id" = ? LIMIT ?  [["id", 2], ["LIMIT", 1]]
      Person Load (0.2ms)  SELECT "people".* FROM "people" WHERE "people"."id" = 1
      GameView Load (0.2ms)  SELECT "game_views".* FROM "game_views" WHERE "game_views"."person_id" = 1
    => #<GamePlay id: 2, game_id: 1, person_id: 1>
    
    irb(main):008:0> GamePlay.find(2).game_view
      GamePlay Load (0.1ms)  SELECT  "game_plays".* FROM "game_plays" WHERE "game_plays"."id" = ? LIMIT ?  [["id", 2], ["LIMIT", 1]]
      GameView Load (0.2ms)  SELECT  "game_views".* FROM "game_views" INNER JOIN "people" ON "game_views"."person_id" = "people"."id" WHERE "people"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
    => #<GameView id: 2, game_id: 1, person_id: 1>
    

    【讨论】:

    • 这不使用game_id。之前可能值得将 GamePlayGameView 重构为带有 kind 列的多态 PersonGame 模型。
    • 出于这个原因,我一直在考虑合并这些表格。我希望有一种比重构更简单的方法,谢谢你的建议。
    【解决方案2】:

    我遇到了同样的挑战,我使用自定义版本的 Rails 预加载器 https://github.com/2rba/smart_preloader 解决了这个问题

    关联和你描述的完全一样:

    class GamePlay
      belongs_to :game
      belongs_to :person
    
      has_one :game_view, -> (gp) { where(game_id: gp.game_id) }, foreign_key: :person_id, primary_key: :person_id
    end
    

    然后显式调用预加载器为:

    game_plays =GamePlay.all
    ActiveRecord::SmartPreloader.(game_plays, ActiveRecord::CompositeKey.new(:game_view, [:game_id, :person_id])
    

    这与 Rails 默认的 GamePlay.preloads(:game_view) 行为几乎相同,在底层调用 ActiveRecord::Associations::Preloader。唯一的区别是 preloader 显式调用,并且 preloader 稍作修改以支持多键和多态关联。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-08-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-08-14
      • 1970-01-01
      • 1970-01-01
      • 2013-10-20
      相关资源
      最近更新 更多