I am creating the following tables in Rails (with Postgresql):
User
: a user can be in one or more groups and can be the owner of one or more groupsGroup
: a group has an admin (which is one of the users) and one or more usersParticipant
: a participant the table in between the other two to connect them
The models are like this:
#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
After this, I did some migration to change the name of the user_id
that the Group
would have, into admin_id
:
class ChangeForeignKeyForGroups < ActiveRecord::Migration[6.1]
def change
rename_column :groups, :user_id, :admin_id
end
end
So now, my schema
looks like this:
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
The problem is that once I try to create a Group in seeds, I keep having this error:
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}>]>
It seems I am missing the user
, so I try adding it but it throws the same error:
user = User.first
User Load (1.4ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT $1 [["LIMIT", 1]]
=> #<User id: 7, email: "[email protected]", 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}>]>
Am I doing something wrong? Have I structure it wrongly? Thanks!
CodePudding user response:
You simply renamed your foreign key but there's more to it that that. In my migration, I would have done something like this:
class ChangeForeignKeyForGroups < ActiveRecord::Migration[6.1]
def change
remove_reference :groups, :user, foreign_key: true
add_reference :groups, :admin, foreign_key: true
end
end
Mind you, I think the way you did the rename, it probably changes the index and foreign key as well. So you probably don't need to change it.
Then in your group.rb
model you need to change the association:
belongs_to :admin, class_name: "User", foreign_key: :admin_id
Then if you go back to your seeds, you can change your group creation to this:
Group.new(name: "Test Group", description: "blablabal", admin: user)
CodePudding user response:
Ruby on Rails has certain naming conventions for associations between models. I suggest reading about Active Record Associations in the official Rais Guides.
For example, if there is a belongs_to :user
association in a Group
model then Rails expects that there is a user_id
column on the groups
table in the database pointing to the id
of a record in a table named users
.
This allows Ruby on Rails to work without much configuration and it is called convention over configuration.
If you do not want to follow these conventions then you need to configure everything that doesn't follow this convention. In your example, you would need to add foreign_key: 'admin_id'
to both sides of the association to tell Ruby on Rails that you do not want to use the default column naming but admin_id
instead, like this:
class User < ApplicationRecord
has_many :participants, foreign_key: 'admin_id'
# ...
class Group < ApplicationRecord
belongs_to :user, foreign_key: 'admin_id'
# ...
Because of the additional configuration needed in the case of non-default naming I highly suggest not using custom table names or foreign key names. And only use them when the database schema is not under your control. Therefore I suggest reverting the renaming from user_id
to admin_id
.