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



我在Rails中创建以下表(与Postgresql):

  • User:用户可以属于一个或多个组,并且可以是一个或多个组的所有者
  • Group:一个组有一个管理员(它是一个用户)和一个或多个用户
  • Participant:一个参与者在其他两个表之间连接它们

模型如下:

#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

之后,我做了一些迁移,将Group将具有的user_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 $1  [["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对模型之间的关联有特定的命名约定。我建议阅读有关活动记录协会的资料

例如,如果在Group模型中有一个belongs_to :user关联,那么Rails期望在数据库的groups表中有一个user_id列指向users表中记录的id

这允许Ruby on Rails在没有太多配置的情况下工作,它被称为约定优于配置。

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

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

由于在非默认命名的情况下需要额外的配置,我强烈建议不要使用自定义表名或外键名。并且仅当数据库模式不在您的控制之下时才使用它们。因此,我建议将user_id重命名为admin_id

最新更新