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

Time:05-31

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 groups
  • Group: a group has an admin (which is one of the users) and one or more users
  • Participant: 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.

  • Related