Self-Referential Relationships with ActiveRecord Ruby

Yasmine Hartung
There She Codes
Published in
4 min readMay 15, 2019

So, let’s talk about ActiveRecord for a second. I want to give a quick shoutout for making all of our lives so much easier, so Thank You ActiveRecord!

Thanks to GIFY

I feel like the best thing that ActiveRecord has given me is it has helped me open up some brain space to think about more complicated relationships instead of focusing on building the basics. One of the things that I had sort of a tough time wrapping my head around is how to write ActiveRecord self-referencing relationships, sometimes called recursive relationships or self joins.

We see a lot of these types of relationships in real life. A very common example is that a Person can be a Parent of many people and is a Child of someone.

ITS COMPLICATED. Thanks to GIFY

A more website-oriented example is that on Twitter or Instagram, a user can follow many users, and also can have many followers.

If we try to do the classic three model with a join table here, we see that we have to create the same user on both sides of the join table (Parent and Child, Follower and Followee), and repeating data is a sign that we haven’t designed things correctly.

So basically, what we want is a model that allows an object to reference itself through different roles (ex: sometimes a Parent, sometimes a Child. Sometimes a Follower, sometimes a Followee)

Something that is important to realize in self referential relationships is that the self referencing class does not hold any information as to which role that instance may be.

That is to say, if we continue with the Twitter example, that nothing in the User class defines the user as a follower or followee. That instance of a user only assumes that role through the join-table, in this case a follow.

If we were to show this relationship visually in a model sketch it would look probably something like this

Thanks to AWW for the Drawing Functionability

There is no actual object called Follower or Followee. These are just different roles that we want to give the object User.

The followers and followees really only exist and are only related to each other through the foreign keys on the join table, follows (followee_ids and follower_ids).

And this makes sense. If we really analyze the issue from the real-world situation, if a user joins Twitter but does not follow anyone and no one is following him, than that user exists, and should be in our database, but it should only have the role of user, and not have the roles of follower or followee.

So how do we set this up with ActiveRecord?

I think the best way to start is to define our join table. Our Follow class belongs to two different roles, followers and followees. Therefore, our join table has to contain at least two foreign keys, one for each role. It may contain other information, but those two foreign keys must be part of the Follow database. This means that the Follow class belongs to two different roles.

We can write this in ActiveRecord like this:

Again, something to keep in mind is if I, user Yasmine wish to follow POTUS, then the foreign keys on this follow should be (follower_id: yasmine.id, followee_id: potus.id). It is the follow that gives me, Yasmine, the role of follower and POTUS the role of followee.

Writing the ActiveRecord for the User class is a little more complicated.

Firstly, I want to say that a user has many followers. We can find a user’s followers by looking in the follow table and finding all the follows where the followee id matches the users id.

How do we tell ActiveRecord this?

Looking through ActiveRecord self-joins, we see that we can set up the relationship like this:

This enables the User class to essentially allow a followee (lets say POTUS) to say potus.followers and ActiveRecord will fetch all the follows where the POTUS.id matches the follow.followee_id and and list all the followers who were listed in the follows as follower_id.

Now, we want the counter functionality. We want to say that a user follows many followees. We can do this by matching a user’s id in the follow table with the follower id.

This is how the User class relationships are now defined:

Now we’ve successfully defined this self-referential relationship in ActiveRecord! PAR-TAYY!

Thanks to GIFY

Sources:https://guides.rubyonrails.org/association_basics.html#self-joins

--

--