ruby-on-rails Rails使用连接表查找记录并提取单列

djmepvbi  于 2023-05-30  发布在  Ruby
关注(0)|答案(1)|浏览(185)

我有一个多对多的关系:

Player 
  has_many :team_players
  has_many :teams, through: :team_players

Team
  has_many :team_players
  has_many :players, through: :team_players

TeamPlayer
  belongs_to :team
  belongs_to :player

玩家可以执行的一个动作是加入一个团队。这只会创建一个新的TeamPlayer记录,其中包含team_id + player_id(状态为“active”)。当一个玩家加入一个团队时,我希望端点响应玩家+该玩家所在的所有团队。
在玩家加入团队时,我还没有检索到玩家记录,我用team_id + player_id创建了加入记录。
我知道我可以先得到球员,然后做一些类似于player.teams的事情,但是我想学习如何用teams记录获取适当的球员(整个球员记录),并且只从Team表中提取'name'列。
我试过Player.find(player_id).joins(:teams).pluck(:name),但这不起作用。我觉得连接的地方不对。我在想这样的事情:
Player.joins(:teams).pluck(:name)但不知道在哪里放播放器ID。会是where从句吗
我在搜索时也看到了这样的语法:Player.teams.where...但也不知道如何使用。
所以基本上我希望最终的结果是这样的:

player {
  // player fields...
  teams: ['team-1', 'team-2', etc...],
}

编辑
这个查询起作用了:

Player.where(id: player_id).includes(:teams).pluck(:name).first

但这只返回一个球队名称数组(球员是其中的一部分)。我希望球队列表嵌套在球员对象中
---最终模型---
对于任何好奇的人来说,这就是最终模型的样子:

class Player < ApplicationRecord
    # relations
    # - need this association to do join queries
    has_many :team_players
    has_many :teams, through: :team_players
    has_many :active_team_players,
        -> { where(status: TeamPlayer.status[:active]) },
        class_name: 'TeamPlayer'
    has_many :active_teams,
        through: :active_team_players,
        source: :team

    def self.get_with_teams(player_id)
        Player
            .left_joins(:active_teams)
            .group(:id)
            .select(
                'players.*',
                'json_agg(teams.name) AS team_names'
            )
            .find(player_id)
    end
end
cmssoen2

cmssoen21#

不是你想要的

pluck用于立即触发数据库查询,并仅以数组的形式获取所选列。除非它在一个已经加载的关系上被调用,实际上就像#map一样工作。

这是一个基于表的世界

如果你想从一个连接的表中选择多个值到结果中的一个列中,你需要一个聚合函数。例如,在Postgres上,你可以选择一个包含所有团队名称的数组:

player = Player.left_joins(:teams)
           .group(:id)
           .select(
             'players.*',
             'json_agg(teams.name) AS team_names'
           ) 
           .find(1)

player.team_names # ["foo", "bar", "baz"]

在其他数据库上,您可能需要使用其他聚合函数。但是,您可能希望保留这种方法,直到出于性能原因而实际需要使用它,并使用.includes/.eager_load
Player.eager_load(:teams)实际上选择了两个表中的列-实际上为每个团队选择了一行,以及来自玩家的列:

SELECT "players"."id" AS t0_r0, 
       "players"."created_at" AS t0_r1, 
       "players"."updated_at" AS t0_r2, 
       "teams"."id" AS t1_r0, "teams"."name" AS t1_r1, 
       "teams"."created_at" AS t1_r2, "teams"."updated_at" AS t1_r3 
FROM "players" 
LEFT OUTER JOIN "team_players" ON "team_players"."player_id" = "players"."id" 
LEFT OUTER JOIN "teams" ON "teams"."id" = "team_players"."team_id"

结果中的每一行都将类似于:

t0_r0 (players.id) | t1_r0 (teams.id) | t1_r1 (teams.name) 
-------------------------------------------------------------
1                  | 1                | Arsenal
1                  | 2                | Tottenham
1                  | 3                | AC Milan
2                  | 4                | AFC Ajax
2                  | 1                | Arsenal

Rails实际上会循环遍历结果,并将其全部填充到Player和Team记录中,以便您可以专注于更有趣的内容:

class Player < ApplicationRecord
  # ...

  def team_names
    teams.map(&:name)
  end
end
player = Player.eager_load(:teams)
                .id(1)
puts player.team_names

Scoping关联

所有上述仍然只是得到所有的球队。如果您只想要活跃的团队,则可以添加其他关联:

class Player < ApplicationRecord
  has_many :team_players
  has_many :teams, through: :team_players
  has_many :active_team_players,
    ->{ where(active: true ) },
    class_name: 'TeamPlayer' 
  has_many :active_teams, 
    through: :active_team_players,
    source: :team
end

当你连接active_teams时,它会在join子句中添加一个条件:

irb(main):003:0> Player.joins(:active_teams)
  Player Load (0.8ms)  SELECT "players".* FROM "players" INNER JOIN "team_players" ON "team_players"."active" = $1 AND "team_players"."player_id" = "players"."id" INNER JOIN "teams" ON "teams"."id" = "team_players"."team_id"  [["active", true]]
=> []

相关问题