Rails - 即使在添加外键后也无法创建实例

Rails - Not being able to create an instance even after adding foreign key

我正在 Rails 中创建以下 tables(使用 Postgresql):

模型是这样的:

#User.rb

class User < ApplicationRecord
  has_many :groups
  has_many :participants
end
#Group.rb

class Group < ApplicationRecord
  belongs_to :user
  has_many :participants
end
#Participant.rb

class Participant < ApplicationRecord
  belongs_to :group
  belongs_to :user
end

在此之后,我做了一些迁移,将 Groupuser_id 的名称更改为 admin_id:

class ChangeForeignKeyForGroups < ActiveRecord::Migration[6.1]
  def change
    rename_column :groups, :user_id, :admin_id
  end
end

所以现在,我的 schema 看起来像这样:

  create_table "groups", force: :cascade do |t|
    t.string "name"
    t.text "description"
    t.integer "participants"
    t.bigint "admin_id", null: false
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.index ["admin_id"], name: "index_groups_on_admin_id"
  end

  create_table "participants", force: :cascade do |t|
    t.bigint "group_id", null: false
    t.bigint "user_id", null: false
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.index ["group_id"], name: "index_participants_on_group_id"
    t.index ["user_id"], name: "index_participants_on_user_id"
  end

  create_table "users", force: :cascade do |t|
    t.string "email", default: "", null: false
    t.string "encrypted_password", default: "", null: false
    t.string "reset_password_token"
    t.datetime "reset_password_sent_at"
    t.datetime "remember_created_at"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.string "username"
    t.index ["email"], name: "index_users_on_email", unique: true
    t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true

问题是,一旦我尝试在种子中创建一个组,我就会一直出现此错误:

pry(main)> group = Group.new(name: "Test Group", description: "blablabal")
=> #<Group:0x00007fdbcdd03f80 id: nil, name: "Test Group", description: "blablabal", participants: nil, admin_id: nil, created_at: nil, updated_at: nil>
[5] pry(main)> group.valid?
=> false
[6] pry(main)> group.errors
=> #<ActiveModel::Errors:0x00007fdbcdc7ad98
 @base=#<Group:0x00007fdbcdd03f80 id: nil, name: "Test Group", description: "blablabal", participants: nil, admin_id: nil, created_at: nil, updated_at: nil>,
 @errors=[#<ActiveModel::Error attribute=user, type=blank, options={:message=>:required}>]>

我好像遗漏了 user,所以我尝试添加它,但它抛出了同样的错误:

user = User.first
  User Load (1.4ms)  SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT   [["LIMIT", 1]]
=> #<User id: 7, email: "jose@test.io", created_at: "2022-05-31 10:29:06.208673000 +0000", updated_at: "2022-05-31 10:29:06.208673000 +0000", username: "Jose">

pry(main)> group = Group.new(name: "Test Group", description: "blablabal", admin_id: user.id)
=> #<Group:0x00007fdbdd205ad8 id: nil, name: "Test Group", description: "blablabal", participants: nil, admin_id: 7, created_at: nil, updated_at: nil>
[13] pry(main)> group.valid?
=> false
[14] pry(main)> group.errors
=> #<ActiveModel::Errors:0x00007fdbdd1d4578
 @base=#<Group:0x00007fdbdd205ad8 id: nil, name: "Test Group", description: "blablabal", participants: nil, admin_id: 7, created_at: nil, updated_at: nil>,
 @errors=[#<ActiveModel::Error attribute=user, type=blank, options={:message=>:required}>]>

我是不是做错了什么?我的结构有误吗?谢谢!

您只是重命名了您的外键,但除此之外还有更多。在我的迁移中,我会做这样的事情:

class ChangeForeignKeyForGroups < ActiveRecord::Migration[6.1]
  def change
    remove_reference :groups, :user, foreign_key: true
    add_reference :groups, :admin, foreign_key: true
  end
end

请注意,我认为您进行重命名的方式可能还会更改索引和外键。所以你可能不需要改变它。

然后在您的 group.rb 模型中您需要更改关联:

belongs_to :admin, class_name: "User", foreign_key: :admin_id

然后如果你回到你的种子,你可以将你的组创建更改为:

Group.new(name: "Test Group", description: "blablabal", admin: user)

Ruby on Rails 对模型之间的关联有一定的命名约定。我建议阅读 Active Record Associations 在官方 Rais 指南中.

例如,如果 Group 模型中有一个 belongs_to :user 关联,那么 Rails 预计 groups 上有一个 user_id 列数据库中的 table 指向 table 中名为 users.

的记录的 id

这允许 Ruby 在 Rails 上工作而无需太多配置,它被称为 convention over configuration

如果您不想遵循这些约定,那么您需要配置不遵循此约定的所有内容。在您的示例中,您需要将 foreign_key: 'admin_id' 添加到关联的两侧,以告诉 Rails 上的 Ruby 您不想使用默认列命名,而是 admin_id相反,像这样:

class User < ApplicationRecord
  has_many :participants, foreign_key: 'admin_id'
  # ...

class Group < ApplicationRecord
  belongs_to :user, foreign_key: 'admin_id'
  # ...

由于在 non-default 命名的情况下需要额外的配置,我强烈建议不要使用自定义 table 名称或外键名称。并且仅在数据库模式不受您控制时才使用它们。因此,我建议将重命名从 user_id 还原为 admin_id