Confused about “has_and_belongs_to_many”? Here’s what it looks like in the database…

The “has_and_belongs_to_many” association and its close friend the “has_many through” 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 and a secret, hidden database table. Couple that with the fact that many experienced Rails developers treat “has_and_belongs_to_many” as the deprecated, weaker sibling of “has_many through” when they actually represent similar but different things and it’s no wonder people get frustrated.

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 represent people assigned to teams. Each team has many people assigned to it and each person can be assigned to many teams. But, importantly, we only care that someone is assigned to a team, we don’t care what they’ll actually be doing on that team.

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!

Relational databases, as a matter of course, only contain “simple” data types; strings, integers and dates etc. Ideally we would model team assignments as a pair of arrays; a person has an array of teams to which they belong, a team has an array of people who are assigned to it. But we can’t do that directly; instead, Rails cheats, using an intermediary table:

person_id team_id
1 1
1 2
2 2

If you look at this carefully you may spot that Hannah is assigned to Team A and Team B, while James is just on Team B.

This third table will be called people_teams (the names of the two tables in question in alphabetical order) and it doesn’t have its own ID column (which you can specify by passing id: false in your migration).

Our models then look like this:

And Rails goes out of its way to make these look like arrays; to add a person to a team you would do this: @team_a.people << @james or @james.teams << @team_a (whichever makes more sense in your context) and both team and person will be updated accordingly.

So, to recap, relational databases don’t usually have a native array type. To get over this, ActiveRecord simulates one, letting you associate models with other models as if they were connected through arrays.

Contrast that to “has_many through” which is similar but represents a fundamentally different concept.

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