如何从下游对象子集涉及的复杂活动记录 has_many 中获取列表

How do you get a list from complex active record has_many involved from a subset of downstream objects

当在中间关系上实现多个外键时,我很难从分层父关系中获取所涉及的游戏列表。

给定 League 对象 NFC,找到它的所有 Game 对象 [G1,G3,G4]

#  id           :integer          not null, primary key
#  name         :string
class League
  has_many :teams
  # has_many :games, :through => :teams (Is there some way to do this?)
end

#  id         :integer          not null, primary key
#  team_name    :string
#  league_id :integer
class Team
  belongs_to :league
  has_many :home_games, :foreign_key => team_a_id, :source => :game
  has_many :away_games, :foreign_key => team_b_id, :source => :game
end

#  id                   :integer          not null, primary key
#  game_name            :string
#  team_a_id :integer          not null
#  team_b_id :integer          not null
class Game
  belongs_to :home_team, :class_name => Team
  belongs_to :away_team, :class_name => Team
end

数据示例:

LEAGUE - TEAM - GAME 
---------------------------------
AFC - 
        PATRIOTS - 
                 Home       Away   
               G1(PATRIOTS vs DALLAS)
               G2(PATRIOTS vs PITTSBURG)
        PITTSBURG - 
               G2(PATRIOTS vs PITTSBURG)
NFC - 
        DALLAS - 
               G1(PATRIOTS vs DALLAS)
               G3(DALLAS vs GREENBAY)
               G4(DALLAS vs SEATTLE)
        GREENBAY
               G3(DALLAS vs GREENBAY)
        SEATTLE
               G4(DALLAS vs SEATTLE)

答案将包含 Rails 4 合规答案。如果 Rails 4 替代方案效率非常低,则可能会特别考虑 RAILS 5 答案。

nfc = League.where(name: 'NFC').first
# <answer>
puts nfc.games 
##  array containing objects [G1,G2,G3]

我面临的挑战是 home_team / away_team 并结合来自外键的数据。

一个可能的解决方案是在 League 上定义一个 games 方法,该方法查找其中任一外键指向其中一支球队的所有比赛:

class League

  has_many :teams


  def games
    Game.where('team_a_id in (:ids) or team_b_id in(:ids)', ids: teams.pluck(:id))
  end
end

您可以使用 join:

完成同样的事情
Game.joins('inner join teams on teams.id = games.team_a_id or teams.id = games.team_b_id').where('teams.league_id = ?', id) 

我要给出答案, 因为@meagar 的第一个解决方案 需要两个 SQL 查询而不是一个(另外,如果联赛没有球队,那不是 SQL 语法错误吗?), 第二个解决方案将包含重复的 Game 实例 如果两支球队都来自同一个联盟。

总的来说,我尽量避免在我的可重用范围内加入, 因为他们强制查询进入某个 "shape"。 所以我会写这样的东西:

class Game
  # ...
  scope :for_league, ->(league_id) {
    where(<<-EOQ, league_id)
      EXISTS (SELECT  1
              FROM    teams t
              WHERE   t.id IN (games.team_a_id, games.team_b_id)
              AND     t.league_id = ?)
    EOQ
  }
  # ...
end

顺便说一下,这种 SQL 技术被称为 "correlated sub-query"。我承认你第一次看到它时看起来很奇怪,但这是一件很正常的事情。您可以看到子查询 "reaches out" 引用 games。您的数据库优化它应该没有问题(当然给定外键索引),但从概念上讲,它在 games table.

中每行运行一次子查询