How to Create a Model With Two Foreign Keys References From the Same Table in Rails

Shaqqour
3 min readDec 18, 2020

--

Photo by Jaye Haych on Unsplash

Rails is one of the best frameworks to create tables because it simplifies the process of creating and connecting these tables. However, in some situations, it requires slightly complicated steps to set up table relations. Let's take a money transfer example on a platform such as Venmo or Paypal. We would have User and Payment models/tables. They have a one-to-many relationship. A user has many payments and a payment belongs to two users, a payer, and a payee. So the Payment would require two references (user IDs) from the user table to be able to recognize the payer from the payee in a payment transaction. Therefore, we need to create a Payment model with two foreign keys from the User model.

Generating the payment model

Rails provides different kinds of generators; I will be writing another blog post explaining each one in detail. We will be using the Model Generator to create our Payment model that has an amount, transaction_time, payer_id, and payee_id:

$ rails generate model Payment amount:integer transaction_time:datetime payer_id:references payee_id:references

This will create for you the following:
- Payment model under app/models
- Migration to create the payment table under db/migrate
- Test files under test/models

Let's take a look at our migration that was created from the generator

class CreatePayments < ActiveRecord::Migration[6.0]
def change
create_table :payments do |t|
t.integer :amount
t.datetime :transaction_time
t.references :payer_id, null: false, foreign_key: true
t.references :payee_id, null: false, foreign_key: true

t.timestamps
end
end
end

Now let's run rails db:migrate to create our beloved table…

..
.
Oh no, it didn’t work. Of course, it isn’t going to work. You are trying to add references as foreign keys from two tables that don’t exist, the payer table and the payee table. Remember that the payer and payee are both instances from the User table, so you would have to specify that in your migration. Let’s fix our migration to specify what table these references are from

class CreatePayments < ActiveRecord::Migration[6.0]
def change
create_table :payments do |t|
t.integer :amount
t.datetime :transaction_time
t.references :payer_id, null: false #remove foreign
t.references :payee_id, null: false #remove foreign
t.timestamps
end
add_foreign_key :payments, :users, column: :payer_id
add_foreign_key :payments, :users, column: :payee_id

end
end

Now you can run rails db:migrate and create the payment table that has two foreign keys from the user table. To make sure the table was created correctly, open the schema file in your db folder and you should see a payment table similar to this:

create_table "payments", force: :cascade do |t|
t.integer "amount"
t.datetime "transaction_time"
t.integer "payer_id", null: false
t.integer "payee_id", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["payer_id"], name: "index_payments_on_payer_id"
t.index ["payee_id"], name: "index_payments_on_payee_id"
end
add_foreign_key "payments", "users", column: "payer_id"
add_foreign_key "payments", "users", column: "payee_id"

Now, you have to specify the relationship in each of the models, the Payment and the User models. Go to these two models under app/models and make the following changes:

class Payment < ApplicationRecord
belongs_to :payer, class_name: 'User'
belongs_to :payee, class_name: 'User'

end

class User < ApplicationRecord
has_many :payer_payments, class_name: 'Payment',
foreign_key: 'payer_id'
has_many :payee_payments, class_name: 'Payment',
foreign_key: 'payee_id'

end

If you want to make sure everything is working as expected, open rails console by running rails c and create payments and users.

user1 = User.create(name: 'Mike', email: 'mike@email.com')
user2 = User.create(name: 'Jake', email: 'jake@email.com')
payment1 = Payment.create(amount: 100, transaction_time: Time.Zone.now, payer_id: user1.id, payee_id: user2.id)

In this examplepayment1 belongs to two users, user1 as the payer and user2 as the payee.

I hope this helped you in your rails application and taught you how to create a model with two foreign keys from the same table. If you have anything you would like to add, please comment below.

--

--

Shaqqour
Shaqqour

Written by Shaqqour

Full stack software engineer. Passionate about making people’s lives better and easier through programming. LinkedIn.com/in/shaqqour

Responses (2)