Self -Join

An Active Record association that links to itself.

Prerequisite:

Table of Contents

The Foundation

An Example

Creating a Table

Association
belongs_to
has_many

Creating Data

The Foundation

All the 6 basic associations that I covered in Part 1, Part 2 & Polymorphic had one thing in common, all associations required more than 1 or in some cases more than 2 models to be associated with each other. However, to use Self-Join association, you require only 1 model.

So what is it?

A Self-Join association helps us to associate each instances within the same table through a foreign-key which references to its own primary-key.

An Example

Let's elaborate on the definition of Self-Join with an example to better grasp the concept.

A classroom has some leaders, where each leader has a group of students to complete a project.

Here, instead of creating 2 tables of leaders and students with has_many-belongs_to association, we can set the same relation with just a single table, because even a leader is a student of a classroom just as other students, and giving 2 different tables would create duplicate entries of leaders.

So, does this mean we would need to give has_many-belongs_to association to the same table? But how? Let’s go through it step-by-step…

An overview of the example

Creating a Table

First, we would create a table named students where records of both group-leader and students will be present. We will set a couple of attributes to the table of name & project_title.

Here’s the twist, how would each record or student know who’s his/her group project leader? Simple, with the help of a foreign key. Usually, for other associations, a foreign key value is set to its opposite table but here we set the foreign key to the students table itself.

The naming of a foreign key can be set to anything of relevance to what we are assigning-to to that record. Here each record is a student of a classroom who has a leader, so an appropriate name would be ‘group leader’.

Now, let's create the table in the database:

rails g model Student name:string project_title:string grp_leader:references

Running migration will create the following migration file:

Line #6 has been modified to let rails know in which table to find the foreign-key, grp_leader. More on this in Unconventional Foreign-key.

After migrating or creating a table in the database with rails db:migrate command, now let's see how to set association.

Association

Let's go through this step-by-step:

belongs_to:

Since we know each student or record in the students table belongs to a leader, we give an appropriate association name with the addition of class_name option to it to let rails know to look for foreign-key in students table. More on this in Unconventional Foreign-key.

However, not all records will have a foreign-key value set. Those records who are leaders, who don’t belong to any other leader will have an empty value in their foreign-key attribute. Rails does not allow to save a record without a foreign key value, so the :optional option is set to true to let rails allow records to be saved.

has_many:

Since each record belongs_to a leader, then a leader has many members. So we set has_many with appropriate association name and again we let rails know in which table to look for records with class_name option. And adding an additional option of foreign_key so rails knows to look fora grp_leader_id attribute instead of grp_members_id.

Data

After setting proper association, let's fill some records to our table through the rails console:

Creating leaders’ data:

# leader_1 = Student.create(name: "Sakina", project_title: "Climate Change")

Now, creating members for this project:

# leader_1.grp_members << Student.create(name: "Zainab", project_title: "Climate Change")

Extracting all group members of a leader

# leader_1.grp_members
=> [#<Student id: 2, name: "Zainab", project_title: "Climate Change", grp_leader_id: 1, created_at: "##", updated_at: "##">]

Extracting a group members’ leader:

# grp_member = Student.find_by(name: "Zainab")# grp_member.grp_leader
=> <Student id: 1, name: "Sakina", project_title: "Climate Change", grp_leader_id: nil, created_at: "##", updated_at: "##">

The value of a foreign key for records of a leader is set to nil.

I hope I have achieved to make you understand the topic thoroughly. If you have any questions or other comments, feel free to leave them below. Also, if you found this article useful, you can share it so others can find it as well.