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:
Pretty simple, huh?
We also need our list of teams:
Imaginative names in this organisation!
Finally, our actual role assignments:
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:
class Person < ActiveRecord::Base
class Role < ActiveRecord::Base
class Team < ActiveRecord::Base
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.