Rails 一个模型中的两个多态关联

Rails two polymorphic associations in one model

我有以下模型:GameHighSchoolTeamClubTeam。我想要 Game 两个有一个 team_one 和一个 team_two 字段,每个字段引用一个 HighSchoolTeam 或一个 ClubTeam.

HighSchoolTeamClubTeam 我有 has_many :games, as: :teamable。在 Game 我想做类似下面的事情...

class Game < ApplicationRecord
  belongs_to :team_one, polymorphic: true, class_name: "Teamable"
  belongs_to :team_two, polymorphic: true, class_name: "Teamable"
end

...但是 class_name: "Teamable 部分似乎不起作用。


编辑:

schema.rb

ActiveRecord::Schema.define(version: 2019_12_24_011346) do
  ...
  create_table "club_teams", force: :cascade do |t|
    t.string "name"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
  end

  create_table "fields", force: :cascade do |t|
    t.string "name"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
  end

  create_table "games", force: :cascade do |t|
    t.bigint "tournament_id", null: false
    t.string "team_one_type", null: false
    t.bigint "team_one_id", null: false
    t.string "team_two_type", null: false
    t.bigint "team_two_id", null: false
    t.bigint "field_id", null: false
    t.date "date"
    t.datetime "start_time"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.index ["field_id"], name: "index_games_on_field_id"
    t.index ["team_one_type", "team_one_id"], name: "index_games_on_team_one_type_and_team_one_id"
    t.index ["team_two_type", "team_two_id"], name: "index_games_on_team_two_type_and_team_two_id"
    t.index ["tournament_id"], name: "index_games_on_tournament_id"
  end

  create_table "high_school_teams", force: :cascade do |t|
    t.string "school_name"
    t.string "team_name"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
  end

  create_table "tournaments", force: :cascade do |t|
    t.string "name"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
  end

  add_foreign_key "games", "fields"
  add_foreign_key "games", "tournaments"
end

game.rb

class Game < ApplicationRecord
  belongs_to :tournament
  belongs_to :team_one, polymorphic: true
  belongs_to :team_two, polymorphic: true
  belongs_to :field, optional: true
end

high_school_team.rb

class HighSchoolTeam < ApplicationRecord
  has_many :players
  has_many :games, as: :teamable, dependent: :destroy

  def name
    self.school_name
  end
end

club_team.rb

class ClubTeam < ApplicationRecord
  has_many :players
  has_many :games, as: :teamable, dependent: :destroy
end

控制台输出

code/scout-db [master●] » rails c --sandbox
Running via Spring preloader in process 48525
Loading development environment in sandbox (Rails 6.0.1)
Any modifications you make will be rolled back on exit

WARNING: This version of ruby is included in macOS for compatibility with legacy software.
In future versions of macOS the ruby runtime will not be available by
default, and may require you to install an additional package.

irb(main):001:0> game = Game.new({ team_one_id: "high-school-team-2", team_one_type: "HighSchoolTeam", team_two_id: "club-team-2", team_two_type: "ClubTeam" })
   (0.2ms)  BEGIN
=> #<Game id: nil, tournament_id: nil, team_one_type: "HighSchoolTeam", team_one_id: 0, team_two_type: "ClubTeam", team_two_id: 0, field_id: nil, date: nil, start_time: nil, created_at: nil, updated_at: nil>
irb(main):002:0> game.team_one_id
=> 0
irb(main):003:0> game.save
   (0.3ms)  SAVEPOINT active_record_1
  HighSchoolTeam Load (0.4ms)  SELECT "high_school_teams".* FROM "high_school_teams" WHERE "high_school_teams"."id" =  LIMIT   [["id", 0], ["LIMIT", 1]]
  ClubTeam Load (0.3ms)  SELECT "club_teams".* FROM "club_teams" WHERE "club_teams"."id" =  LIMIT   [["id", 0], ["LIMIT", 1]]
   (0.4ms)  ROLLBACK TO SAVEPOINT active_record_1
=> false
irb(main):004:0> game.errors.full_messages.inspect
=> "[\"Tournament must exist\", \"Team one must exist\", \"Team two must exist\"]"

(2, Syosset, Braves, 2019-12-31 01:07:41.367913, 2019-12-31 01:07:41.367913) 存在于 high_school_teams table 并且 (2, Foobars, 2019-12-31 01:07:52.697821, 2019-12-31 01:07:52.697821) 存在于 club_teams table.

当然 class_name: "Teamable" 不起作用,因为多态关联的全部要点是 class 即 class(更重要的是目标 table)该协会是动态的。也不需要。

多态关联使用单独的 association_name_type 字符串列,其中包含与其关联的 class 名称。

给定以下数据:

| id | team_one_id | team_one_type # ...
----------------------------------
1    |  1          | "HighSchoolTeam"
2    |  2          | "ClubTeam"
3    |  3          | "HighSchoolTeam"

当你做Game.find(1).team_one时,Rails知道使用HighSchoolTeamclass并加入high_school_teamstable。但是它需要在建立连接之前拉出行,当然你的数据库对关系一无所知,也不会保持参照完整性。

所以你只需要:

class Game < ApplicationRecord
  belongs_to :team_one, polymorphic: true
  belongs_to :team_two, polymorphic: true
end

并确保在 games table.

上有 team_one_typeteam_two_type 字符串列

class_name 应该与数据库中的 class 名称匹配。