Ruby: A 30,000-Foot View of Has-Many-Through Associations

Mark Ghaida
3 min readDec 1, 2020

Boy oh boy, where do I start?

Truth be told, this concept REALLY tripped me up when I was first introduced to it.

However, after a couple of hours of dissecting each line of code, I slowly became a Has-Many-Through guru!

Before diving deep, let's first describe three different classes we want and their relationships:

1) Doctor: Has-Many appointments, and Has_Many patients, BUT it only Has-Many patients THROUGH the Appointment class

2) Appointment: Belongs_to both the Doctor and Patient class

3) Patient: Has-Many appointments, and also Has_Many doctors, BUT it only Has-Many doctors THROUGH the Appointment class

Without getting too lost about what a class is and how each class relates to each other, let's first get a 30,000-foot view of what we are trying to accomplish. Here’s a quick story to provide more context:

Let's say we are building a mobile app, allowing doctors and patients to schedule an appointment that fits their schedule.

At some point when building this mobile app, we will have to create three classes that regularly interact with each other: doctors, patients, and appointments.

The reason we need these classes is that it will make life a lot easier when a new doctor and patient sign up on our app.

When Dr. Thompson from Springfield Family Practice arrives at the question “Are you a doctor or patient?” when signing up for our app, him selecting “doctor” will ensure that his profile looks different than if he selected “patient”.

All doctors that sign-up have the same options and settings on their profile, whereas patients have totally different options and settings.

A doctor's profile contains info about their practice, all of his appointments for the day, sensitive medical documents, etc.

A patient's profile contains info about all of their visits, co-pay balances they have, the option to schedule an appointment, etc.

As you can see, doctors do not have access to all the data inside a patient's profile. And vice-versa.

This is good, except for the fact that we want a doctor to have SOME data on their patient, and for a patient to have SOME data about their doctor.

For example, a doctor should have access to their patients' name, their medical documents, their last visit, etc.

A patient should have access to their doctor's schedule, their name, their last visit with her, etc.

The only reason the doctor and patient should have info about each other is if they have an appointment with each other.

The fact they have an appointment with each other should allow them to know more about each other…ONLY if they have an appointment with each other.

Because of this dilemma, it makes sense to create a relationship between the Doctor class, and Patient class, through their common appointment. This is the Has-Many-Through model.

Doctors can get access to their patients' info, only through the Appointment class, aka through their common appointment. And vice-versa.

Put another way, the main purpose of even creating a Has-Many-Through relationship is because we have two classes (doctors and patients) that would otherwise not even have access to each other's data. However, now they do because of the Appointment class.

I like to think of the Appointment class, or other “joiner” classes, as gatekeepers. They prevent us from access to the other side unless we have the right key.

The right key comes in the form of the proper macro’s (attr_accessors, attr_readers, etc.) and the correct methods.

All of these more technical concepts are reserved for part 2.

Stay tuned!

--

--