How Do I Have Many Relationships with Myself?

Mary Rachael Koenke
6 min readNov 8, 2020

Self-Referential Associations

When starting to learn about associations in Active Record and Ruby on Rails, the rule of thumb was when you come across a many-to-many relationships, create a join class in between. This allows you to break the relationship down into two basic has_many/belongs_to relationships and everything is smooth sailing.

And then my whole boat was rocked.

One morning, while attending the Flatiron School, the day began with a discussion question challenging us to think about and model out a User class having a many-to-many relationship with another user, a user following another user, and a user being followed by another user.

…..Self-referential what??

After an intense discussion and throwing a lot of ideas around with my group, the question continued to plague me. How can this actually be implemented in code? After dreaming about the issue, and finding some time to catch my breath amidst the intense bootcamp, I was on a mission.

Turns out its not as complicated as I thought it would be! (me?!? overcomplicate something?!?)

I challenge you to stay with me here and I’ll explain…

Let’s start with a User class. The user can send messages to many other users or receive messages from many other users, so we have a many-senders-to-many-receivers relationship all within the User class. If we just think about it as a many-to-many relationship, what do we need? A join model, of course!

Let’s call the Join model “Message.” Makes sense, because what is connecting a user to another user? A message! If we separate the sender as “User_1” and the receiver as “User_2,” the model looks like something very familiar to us.

Like any join model, it has to hold the foreign keys for the sender (User_1) and the receiver (User 2). The only difference here is that both foreign keys are going to come from the same class. So we actually have something like this:

Confused yet? Lets break it down…

In the Message model, we know the foreign keys are held for the users that it belongs_to. So we can model the Message class as shown below.

So we are saying an instance of message will belong to a sender instance and a receiver instance. However, both of those instances are coming from the User model, so we to specify the class_name. Although a message is expecting a sender_id and a receiver_id, it is actually receiving two user_id, as they both come from the User class.

Now let’s check out our User class and add some code.

Whoa… just breathe… we’re going to take it piece by piece, line by line.

In writing this code, the first line is saying that a user instance has_many messages, specifically “sent_messages” (aptly naming this direction of the relationship as we will get to received messages in a minute). We then assign the foreign key for this relationship as sender_id (instead of a user_id), exactly what the Message instance is looking for (like we said above). Lastly, because we are using “sent_messages” instead of “messages” we have to specify that it is coming from the Message class (just like we did with our naming of “sender” and “receiver” on the Message model). The second line finishes the has_many_through association back to the receiver (the user that is receiving the message).

Now we have to add a bit more code to map out the inverse relationship. A user can receive messages, not just send them!

We do the same thing as before, simply mapping out the opposite flow.

Since we are working with two different kinds of users, we have to name them “sender” and “receiver,” and since we are working with two different kinds of messages, we have to name them “sent_messages” and “received_messages.” As long as we tell Ruby where to look for the information she needs to do her magic, i.e. in which class to look and the names of the foreign keys, she stays happy.

Now the real magic happens when we test this out.

Let’s create a few instances of Users.

I put the byebug in the seeds.rb file to open the console to test the relationships.

In the console, we can check to see if the associations worked by calling the senders and receivers methods on different user instances. We expect for it to return an empty array since we have not created any Message instances.

So now we can create some Message instances. Let’s pretend Joe Biden is sending Kamala Harris and Donald Trump a message.

Now we can use the “receivers” method and call it on our “biden” instance to find out who were the receivers of the messages that Joe Biden sent. We expect to see the objects of Kamala Harris and Donald Trump returned.

Yay! It worked! Joe Biden was able to send his messages to Kamala Harris and Donald Trump. We know they received them!

Now let’s use the “senders” method to see if the inverse relationship worked and that Kamala Harris and Donald Trump know who sent them the message. We expect to see the object of Joe Biden returned.

Indeed, they know Joe Biden was the one who sent the messages! I bet the content of those messages were drastically different!

We have successfully established a self-referential relationship between User and Message!

I tried to learn this self referential relationship with User following other users and users being followed by other users, but before long I was semantically satiated and the words “user” and “follow” no longer made any sense. I hope in using the words “sender,” “receiver,” and “message” allowed for you to follow along a little easier and make some sense out of it!

Now go! Try it for yourself! You can do it!

References:

--

--