josegp
josegp

Reputation: 569

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

I am creating the following tables in Rails (with Postgresql):

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!

Upvotes: 1

Views: 163

Answers (2)

spickermann
spickermann

Reputation: 106972

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.

Upvotes: 1

Douglas Berkley
Douglas Berkley

Reputation: 36

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)

Upvotes: 1

Related Questions