Which ActiveRecord association should I use? “Has and Belongs to Many” or “Has Many Through”?

One of the things many newcomers to Rails (especially those with no relational database experience) find confusing is choosing between two of ActiveRecord’s association methods – the infamous “has_and_belongs_to_many” in comparison to “has_many :through”.

But don’t worry; you’re not being dumb.

They actually look very similar, but do two different things, and which one you choose depends upon your exact requirements (continued below).

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

For example, imagine you need the following:

  • The system maintains a list of different languages that documents can be published in.
  • The system has a number of users.
  • Each user can choose which languages they can publish in; some users will never publish content, so they will not choose a language, the majority will choose just a single language, but some will choose multiple languages.

This is a classic “many-to-many” relationship – each user has zero or more languages, each language has zero or more users. A user choosing a language is an either/or choice; you either tick it or you don’t and there are no further decisions to be made.

And because that’s all there is, we want a “has-and-belongs-to-many” association here.

And don’t forget, when setting up your migrations, you need to add in a join table – these are named by using the two model names in alphabetical order. We’ll also add in a unique index to make searching faster, as well as ensuring that any bugs in our app can’t inadvertantly assign a user the same language twice.

So, to recap, use has-and-belongs-to-many when:

  • one model is related to zero or more of the other model
  • that’s all there is to it; they are either related or they are not

So when do we use has-many :through?

Now imagine, our system also has the following requirements:

  • The system maintains a list of organisations.
  • Each user belongs to zero or more organisations, and likewise, each organisation has zero or more users.
  • When a user is associated with an organisation, they have a set of permissions, specific to that organisation. For example, user A may have “author documents” and “publish documents” permissions for organisation B, but only “author documents” permissions for organisation C.

On the surface, it looks like a classic many-to-many relationship, as above. Zero or more figures on both sides of the equation. But we also need to store these permissions, and they are unique to that particular user and organisation.

Because of this extra bit of data, we actually need an extra model to sit between organisations and users. Let’s call it a “role” for now (although a “permission” would be a good candidate).

Again, when building our migrations, we will probably want an index on our roles table; to speed things up and to make sure users don’t get assigned more than one set of permissions to a given organisation.

A couple of things to note here.

Firstly, a user has-many roles and an organisation has-many roles. In both cases, we’ve added in “dependent: :destroy” – which means that if a user or organisation is deleted, then any associated roles are deleted too. If an organisation is deleted, the users themselves will be untouched, just the role records removed; and vice versa.

Secondly, because of this extra model sat in-between users and organisations, if we want to find out which organisations a user belongs to without caring about their actual permissions, we can use our mysterious has_many :through association.

What this does is find all the user’s roles, then find the organisations associated with those roles and return them to you in a single collection. You could do it by hand, but it’s just a convenience method (which also neatly compresses the database query into a single statement; something that could have a big performance impact if you forget to do it by hand on a large dataset).

As a recap, use has-many :through when:

  • one model is related to zero or more of the other model
  • but there is extra data describing the nature of the association between the two models that we express using a third model

Hopefully, that should make things a bit clearer.

If not just drop me an email (rahoul at theartandscienceofruby.com); or just drop your email address in the box below and I’ll be in touch next time I’ve got some Ruby or Rails help for you.

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