Rails Association — Part 2
An elaboration of
has_many-:through & HABTM associations.
Table of Contents
has_one-:through associations are discussed in my previous article. In this article, we will discuss,
has_manyassociation links declaring models’ each instance to zero or more instances of another model.
In other words, it sets a one-to-many relationship between instances of different models. Usually, the opposite side of the
has_many model will have a
belongs_to association which together build a
Let’s understand this with an example:
“A school has many students and a student belong to a school.”
We build 2 tables in the database, first of school and second of students which would generate corresponding models, and in those model files we would set associations as follows:
class School < ApplicationRecord
class Student < ApplicationRecord
has_manymethod takes a plural name of the model class we want to link to the declaring model.
After setting up associations, we create some data for both models through the rails console:
$ sc = School.create(name:"Al Wadi Al Kabir", city: "Muscat", country:"Oman")
=> #<School id: 1, name: "Al Wadi Al Kabir", city: "Muscat", country: "Oman, created_at: "##", updated_at: "##">
Creating new students of that school:
$ sc.students.create(name: "Vaishaki", age: 8, grade: 3)
$ sc.students.create(name: "Dean", age: 12, grade: 7)
$ sc.students.create(name: "Arun", age: 13, grade: 8)
Visualizing above data created:
Observing the above table we can say that Al Wadi Al Kabir school has 3 students.
We can get a list of all students of a school by:
=> [#<Student id: 1, name: "Vaishaki", age: 8, grade: 3, school_id: 1, created_at: "##", updated_at: "##">, #<Student id: 2, name: "Dean", age: 12, grade: 7, school_id: 1, created_at: "##", updated_at: "##">, #<Student id: 3, name: "Arun", age: 13, grade: 8, school_id: 1, created_at: "##", updated_at: "##">]
school_id (foreign key) in the
students table represents which school it refers to in the
has_many-:throughassociation links the declaring models’ instance to zero or more instances of another model by proceeding through a third model.
Very similar to the
has_many association, but instead of 2 models, we need 3 models, similar to the
has_one-:through association we saw in the previous article which sets one-to-one relationship.
This association however sets relationship in 2 ways,
many-to-many. It is the declaration of a
belongs_to method in models and a foreign key in the table that differentiate these relationships. Let's take a look at an example of each relationship:
Data: “An airport has many flights taking off where each flight has many passengers in it.”
The association structure is similar to a
has_one-:through relationship, but here, instead of
has_one we set
has_many. So we have our association as follows:
class Airport < ApplicationRecord
has_many :passengers, through: :flights
class Flight < ApplicationRecord
class Passenger < ApplicationRecord
We have 2 tables that include foreign keys which helps link instances with other tables.
Inputting some data to our tables:
$ a = Airport.create(name: "Chennai", sort:"International", city:"Chennai", country:"India")$ a.flights.create(name: "Continental Airlines, INC", carrier_code: "COA", rating:95, seats:150)$ a.flights.create(name: "Air France", carrier_code: "AFR", rating:90, seats:225)$ a.flights
=> <Flight id: 1, name: "Continental Airlines, INC", carrier_code: "COA", rating: 95, seats: 150, airport_id: 1, created_at: "##", updated_at: "##">, #<Flight id: 2, name: "Air France", carrier_code: "AFR", rating: 90, seats: 225, airport_id: 1, created_at: "##", updated_at: "##">$ f = a.flights.second
=> [#<Flight id: 2, name: "Air France", carrier_code: "AFR", rating: 90, seats: 225, airport_id: 1, created_at: "##", updated_at: "##">]$ f.passengers.create(name:"Anik Barwa", group:"economy", seat_no:29)$ f.passengers.create(name:"Prithvi Raj", group:"premium", seat_no:176)$ a.passengers
=> [#<Passenger id: 1, name: "Anik Barwa", group: "economy", seat_no: 29, flight_id: 2, created_at: "##", updated_at: "##">, #<Passenger id: 2, name: "Prithvi Raj", group: "premium", seat_no: 176, flight_id: 2, created_at: "##", updated_at: "##">]
Visualizing the data we created:
The Chennai Airport has 2 flights, named Continental Airlines & Air France, of which Air France has 2 passengers in it, Anik Barwa & Prithvi Raj. Or we could say that the following passengers are of Air France flight which will take off from Chennai airport.
Data: “A teacher takes exams of many students, where each student has many exams to give of different teachers.”
Here, we would have 3 tables,
exams and set their associations as follows:
class Teacher < ApplicationRecord
has_many :students, through: :exams
class Exam < ApplicationRecord
class Student < ApplicationRecord
has_many :teachers, through: :exams
Here a single table,
exams has 2 foreign keys which link to 2 different tables, where
exams tables’ each instance will give us data of that exam for a particular student & of that particular teacher.
Inputting some data into our tables by creating new teachers & students:
$ t1 = Teacher.create(name: "Shweta")
$ t2 = Teacher.create(name: "Raghani")$ s1 = Student.create(name: "Anik Barwa")
$ s2 = Student.create(name: "Prithvi Raj")
1st way to create exam instance through teacher:
$ t1.exams.create(marks: 82, student_id: 1)
2nd way to create exam instance through student:
$ s1.exams.create(marks: 97, teacher_id: 2)
Visualizing the data:
Anik Barwa gave 2 exams from 2 different teachers, and their primary keys are set as foreign keys in
exams table. The combination of foreign keys should always be unique for each instance in
Extracting exam and student data of a teacher:
=> [#<Exam id: 1, marks: 82, teacher_id: 1, student_id: 1, created_at: "##", updated_at: "##">]$ t1.students
=> [#<Student id: 1, name: "Anik Barwa", created_at: "##", updated_at: "##">]
Extracting exam and student data of a student:
=> [#<Teacher id: 1, name: "Shweta", created_at: "##", updated_at: "##">, #<Teacher id: 2, name: "Raghani", created_at: "##", updated_at: "##">]$ s1.exams
=> [#<Exam id: 1, marks: 82, teacher_id: 1, student_id: 1, created_at: "##", updated_at: "##">, #<Exam id: 2, marks: 97, teacher_id: 2, student_id: 1, created_at: "##", updated_at: "##">]
Extracting data through exams:
$ e = Exam.first$ e.student.name
=> "Anik Barwa"$ e.teacher.name
=> "Shweta"$ e.marks
The key difference to set the one-to-many or many-to-many relationships in the
has_many-:through association is in the model file and schema file.
To set one-to-many, we give 2
belongs_to in 2 different model files and their corresponding 2 foreign keys in 2 different tables through migrations.
To set many-to-many, we give 2
belongs_to in a single model file and their corresponding 2 foreign keys in a single table through migration. Sometimes there might be more than 2 foreign keys in an application.
In this example, we declared 3 different tables explicitly with primary keys, however, in the next association, one of the tables will be without a primary key.
This HABTM association, which creates many-to-many relationship, links to zero or more instances of declaring model to another model through a table without the primary key.
Let's thoroughly understand this with an example.
Data: “A Medium Publication has many writers, where a writer can belong to many different Medium Publications.”
We first create 2 tables,
writers and then set association as follows:
class MediumPublication < ApplicationRecord
class Writer < ApplicationRecord
We then create a third table that will only hold the combination of foreign keys of both tables and nothing else, not even a primary key, because it won't be a different entity or model, it's only for binding the 2 other models.
We run the following migration with a
migration task instead of
model and a migration name as
CreateJoinTable which will invoke the
create_join_table method followed by the foreign key attributes we want in the table:
rails g migration CreateJoinTable medium_publication:references writer:references
This will create the following migration file:
The arguments passed to the
create_join_table method is what creates the name of the table in the database. So in this example, it will create a table named
medium_publications_writers. It will create the table name in the same order as arguments are passed to
It's important to note that model names passed to
create_join_table method should be in ascending order meaning rails will expect the initial letters of model names in alphabetical order. In our case, ‘m’ for
medium_publications will be given first followed by ‘w’ for
writers, since ‘m’ comes before ‘w’. For more info on this, visit.
If they’re not passed in alphabetical order, then edit it appropriately. And then run the migration file.
create_join_table method implicitly sets
false, so we have no primary key in our table.
Initializing some data:
First, let's create data for publications and their writers:
$ m1 = MediumPublication.create(username: "geekculture")$ m1.writers.create(username: "juzer-shakir")$ m1.writers
=> [#<Writer id: 1, username: "juzer-shakir", created_at: "##", updated_at: "##">]
Now we create a new writer and assign him to an existing publication:
$ w = Writer.create(username: "lew-brown")$ w.medium_publications << MediumPublication.find_by(username: "geekculture")$ w.medium_publications
=> [#<MediumPublication id: 1, username: "geekculture", created_at: "##", updated_at: "##">]$ m2 = MediumPublication.create(username: "betterprogramming")
Visualizing our data:
The above data tells us that the publication geekculture has 2 writers, juzer-shakir and lew_brown, or in other words these writers belong to geekculture publication. These writers can also belong to betterprogramming publication and many others where their relationships would be captured by the
The combination of foreign keys in the
medium_publications_writers table should always be unique because the table captures the relationship between each instance of
publication table with their primary key, which is always unique for each instance of a table.
These associations are very similar and at times it will confuse us to choose which association and when. Rails official documentation clears this confusion by thoroughly explaining it.
Below is a chart that shows us which association falls into which relationship.
All of the associations that fall into these relationships must be used in combination of
We pluralize the name of the model we are relating for one-to-many & many-to-many relationships and give a singular form of model name to the one-to-one relationship.
In my next article, Rails Association Part 3, I will discuss conventional & Unconventional Foreign-keys, what is association name, and the different methods it generates.