Create a Follow/Following Graph in Rails

Build a sample social graph so users can follow each other

Reinald Reynoso
Jan 13 · 7 min read
Photo by Benjamin Smith on Unsplash

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.


Models

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.

User follows other users

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 Follow model.

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.


Setup

rails new self_ref_practice

Let’s also generate a User model with a username attribute.

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, follower_id and followee_id.

rails g model Follow follower_id:integer followee_id:integer

In the 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 follower_id and followee_id, we are actually looking for a user_id.

With the Follow set up, let’s now set up the User model.

Understanding the wording might be a little tricky at first glance. But let’s break it down.

Users YOU are following

In regards to the Follow model, which attribute determines whether someone is following? The follower_id!

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 has_many :followees.

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, has_many :followers.

Alternative approach with aliasing

Let’s define the first macros as belongs_to :person_doing_the_following with a foreign_key of follower_id.

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 followee_id.

In the User model,

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 followee_id.

Thus, we set up the source to point to the :person_being_followed (foreign_key: followee_id), which is defined in the Follow model.

Conversely, has_many :following_users will also return Follow instances. To determine the User instances of followers, the source is set to the :person_doing_the_following (foreign_key: follower_id).

Seed the database with users

The purpose of the byebug in the seeds.rb is to create Follows while in the terminal.

One last thing, run:

rails db:migrate

Finally, run:

rails db:seed

Creating Follows and Checking for User Followers/Following

Within the byebug of seeds.rb

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 instance.

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:

who.followers

We should get back:

who.followers

And there we go! The instance of who has the one follower of blah. Conversely, let’s run:

blah.followees

We should get back an array of whom blah is following:

blah.followees

We have established self-referential relationship between User and Follow. Let’s create a few more instances of Follow where the other user instances are following who.

Follow instances

Now, let’s check the followers and followees of who.

Followers and Followees of who

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

rails generate model Message sender_id:integer receiver_id:integer message

Then run:

rails db:migrate

Our associations between the User and Message will be similar to the relationship between the User and Follow models.

In 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 Follow. User 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).

Conversely, a User has_many :received_messages, which is determined by the receiver_id attribute of the Message model.

With 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

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",
foreign_key: "receiver_id"
end

And in the User model, let’s define:

Testing the user-message association

Message Creation

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!


Closing Remarks

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.


Resources

Better Programming

Advice for programmers.

Reinald Reynoso

Written by

Software Engineering Coach @ Flatiron School

Better Programming

Advice for programmers.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade