Confused about “has_many through” associations? Here’s what they look like in the database…

The “has_many through” association and its close friend the “has_and_belongs_to_many” association is one of the things that consistently puzzles and confuses newcomers to Rails.

And there’s a simple reason for this … whilst in Rails it is a single association, in the database it represents two database relations. Actually, to be more accurate, the “has_many through” is a short-cut for two Rails associations, but people treat it as synonymous with “has_and_belongs_to_many” which just ups the confusion.

The best way to understand what’s going on under the hood is to think in terms of relations and take an actual look at the database. So let’s run through an example:

Our application is going to model a work-place; we have people who are assigned to teams, with different roles within those teams. So Hannah might be Team Leader in Team A but Senior Developer in Team B.

To start with, we need a people table:

id name
1 Hannah
2 James

Pretty simple, huh?

We also need our list of teams:

id name
1 Team A
2 Team B

Imaginative names in this organisation!

Finally, our actual role assignments:

id person_id team_id role
1 1 1 Team Leader
2 1 2 Senior Developer
3 2 1 Junior Developer
4 2 2 Junior Developer

So, as mentioned earlier, Hannah has two roles; Team Leader on Team A and Senior Developer on Team B. Whilst James also has two roles; he’s a Junior on both teams.

These three tables would be represented in our Rails application as three models:

People are linked to roles, teams are linked to roles and roles are linked to both teams and people.

So where does the “has_many through” come in?

As I said, its not really an association; its a shortcut for other associations. In this case, if we wanted to know which teams Hannah was working in (regardless of her role) we would have to do something like this:

This goes to the database, loads all the role records associated with Hannah, and then goes through them one at a time, loading up the associated team. As you might imagine, this is inefficient as it queries the database repeatedly.

But if we define has_many :teams, through: :roles on our Person model we can write the following code:

Not only is the code much more concise and readable but ActiveRecord can optimise this into a single database query that loads all the relevant teams by “joining” the tables together.

So next time you’re wondering whether a has_many through is the right association for you, remember it’s not a real association; it’s a short-cut for efficiently navigating through multiple database relations.

Do you know what to do but not how it works?

Ever wanted to understand why Rails views work the way that they do? Why variables from your controllers are visible inside your views?

Sign up below to get a free 5 part email course on how Ruby on Rails view rendering works and gain a deep understanding of the Rails magic.

We will send you the course, plus the occasional update from this web-site. But no spam, we promise, and it's easy to unsubscribe