Rails - 即使在添加外键后也无法创建实例
Rails - Not being able to create an instance even after adding foreign key
我正在 Rails 中创建以下 tables(使用 Postgresql):
User
:一个用户可以在一个或多个组中,并且可以是一个或多个组的所有者
Group
: 一个组有一个管理员(是其中一个用户)和一个或多个用户
Participant
: 一个参与者 table 在其他两个之间连接他们
模型是这样的:
#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 [["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
。
我正在 Rails 中创建以下 tables(使用 Postgresql):
User
:一个用户可以在一个或多个组中,并且可以是一个或多个组的所有者Group
: 一个组有一个管理员(是其中一个用户)和一个或多个用户Participant
: 一个参与者 table 在其他两个之间连接他们
模型是这样的:
#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 [["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
。