Create a Follow/Following Graph in Rails
Build a sample social graph so users can follow each other
For the longest time, I avoided the implementation of self-relationships in my projects.
I was always aware of the concept and knew of its application (most famously, a user can follow a certain user but that certain user does not necessarily need to follow that user back), but I could never fully understand or visualize how it worked, despite reading numerous blogs.
The biggest roadblock was that I just never actually coded it…until now.
We will use the following models, a
Follow model, as an example.
The setup of self-referential relationships involves two tables, the user table and a joiner (follow) table that will connect it back to the user table. Sounds confusing? Don’t worry, here’s a little visual for you.
With this relationship, we are saying a user can “follow” many users through the
Follow model, while at the same time, a user can be “followed” by many users through the
Ideally, it makes sense to have one
User model instead of the two that were drawn out. In simpler terms, what we really have is,
This is a many-to-many relationship but the setup will be slightly different.
But first, let’s create a quick Rails app with our models.
rails new self_ref_practice
Let’s also generate a
User model with a
rails g model User username
And finally, let’s generate a
Follow model that keeps track of who is the follower and who is the followee. Let’s provide the following attributes,
rails g model Follow follower_id:integer followee_id:integer
Follow model, we will create the association:
class Follow < ApplicationRecord belongs_to :follower, class_name: “User” belongs_to :followee, class_name: “User”end
It’s saying that an instance of
Follow will belong to two instances, a follower and a followee.
However, both of those instances are coming from the
User model. In other words, even though we expect a
followee_id, we are actually looking for a
Follow set up, let’s now set up the
Understanding the wording might be a little tricky at first glance. But let’s break it down.
Users YOU are following
Let’s begin with
has_many :followed_users. In simple English,
followed_users means users that you are following.
In regards to the
Follow model, which attribute determines whether someone is following? The
Now, the first line,
has_many :followed_users, only returns the instance of the
Follow. We really want the instance of the user we are following. Thus, we define
Users that follow YOU
has_many :following_users, it means these are users that follow you.
In regards to the
Follow model, the attribute that determines whether someone is being followed is the
followee_id. Similarly, we want the instance of the
User, not the follow.
So, we set up the association,
Alternative approach with aliasing
An alternative approach we can take to creating associations between
Follow involves aliasing, where we provide foreign keys in the
Let’s define the first macros as
belongs_to :person_doing_the_following with a
In other words, the person doing the following must be the
follower_id on the
Follow instance. Conversely, the
person_being_followed must be the
Similar to earlier, the
has_many :followed_users will return the instances of the
Follow. To break it down, the
Follow instance points to two users, the one doing the following and the one being followed.
Through the association of
has_many :followees, we want only the
User instance of the
Thus, we set up the source to point to the
foreign_key: followee_id), which is defined in the
has_many :following_users will also return
Follow instances. To determine the
User instances of followers, the source is set to the
Seed the database with users
One final thing to do in the setup, seed the database with some users. In
The purpose of the
byebug in the
seeds.rb is to create
Follows while in the terminal.
One last thing, run:
Creating Follows and Checking for User Followers/Following
Now, inside of
byebug within the
seeds.rb file, we have access to the user instances that were created. The goal is to create follows but determine who is following who.
Just to check if associations worked, we call the followers and followees methods on different user instances. As expected, it should be an empty array since we have not created follows.
Let’s actually create some
Follow instances. But let’s be careful in defining who the follower is and who the followee is to avoid any confusion.
I want the instance of
blah to follow the instance of
who. In the perspective of the follow instance, the follower would be
blah and the person being followed (followee) would be
who. Let’s create the
Follow.create(follower_id: blah.id, followee_id: who.id)
The instance of
Follow gets created. Again, we do not need to know about the follow instance. We use the instance to determine the user instances.
Let’s start with the user instance,
who. This instance should have a follower. Let’s run:
We should get back:
And there we go! The instance of
who has the one follower of
blah. Conversely, let’s run:
We should get back an array of whom
blah is following:
We have established self-referential relationship between
Follow. Let’s create a few more instances of
Follow where the other user instances are following
Now, let’s check the followers and followees of
As we see,
who has many followers but does not follow anyone (popular instance). While I have you here, let’s do a bonus.
Message Other Users
First, let’s do a quick setup. Let’s generate a
rails generate model Message sender_id:integer receiver_id:integer message
Our associations between the
Message will be similar to the relationship between the
Message model, let’s add the following association:
class Message < ApplicationRecord belongs_to :sender, class_name: "User" belongs_to :receiver, class_name: "User"end
Now in the
User model, let’s add a couple of methods.
The language is not as confusing as
has_many :sent_messages which is determined by the
sender_id attribute of the
Message model which will return an array of the messages.
Similarly, setting up the
has_many :receivers will return an array of the
User instances you sent a message to (i.e. received a message from you).
has_many :received_messages, which is determined by the
receiver_id attribute of the
has_many :senders, an array of
User instances is returned that sent a message to you (i.e. messages that you received).
Alternative approach with aliasing
Similar to the alternative approach between
Follow, let’s define the following in the Message model:
class Message < ApplicationRecord belongs_to :person_sending_the_message, class_name: "User",
foreign_key: "sender_id" belongs_to :person_receving_the_message, class_name: "User",
And in the
User model, let’s define:
Testing the user-message association
Let’s now test this association by creating some
Message instances in the
seeds.rb. This will be accomplished through
You may now be asking: “Oh, OK, but how can I get the message and the user associated with it to show?” That’s a challenge you must tackle!
And there you have it, self-referential relationships! What made these concepts difficult for me to accept is the wording. It (embarrassingly) confused me and it still takes some time for me to think about.
Also, I still find it difficult to fully visualize how it is working. But drawing out models and testing it on the console did help improve my understanding. Thank you for reading.