Self -Join
An Active Record association that links to itself.
Prerequisite:
Table of Contents
↦ Association
↪ belongs_to
↪ has_many
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, and 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 like 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…
Creating a Table
First, we would create a table named students
where records of both group leaders 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 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 the rails db:migrate
, we will now take a look on setting up an 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, it will let Rails know to look for the 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 do not allow saving 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 the appropriate association name and again we let rails know in which table to look for records with theclass_name
option. And adding an additional option of foreign_key
so Rails knows to look for a grp_leader_id
attribute instead of a 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 member’s 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 the 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.