One of the challenges I had early in learning Rails was setting up many-to-many relationships between models. Many tutorials and articles cover the simpler one-to-many and many-to-one relationships with ActiveRecord’s has_many and belongs_to functions, respectively, but when trying to build anything more than a sample application you quickly realize that you need to set up slightly more complicated relationships.

Problem

I want to set up a many to many relationship between my user and team models where users can belong to many teams and teams can contain many users. Prior to setting up the relationship, my models are just basic ActiveRecord models generated from the rails model generator.

user.rb model (before)

class User < ActiveRecord::Base
end

team.rb model (before)

class Team < ActiveRecord::Base
end

Solution

To enforce the many to many relationship, I chose to implement the one of the ways suggested by the RailsCast Many-to-Many screencast, which involves has_many :through.

This approach involves creating another model specifically for handling the relationship mappings so that ActiveRecord can join the tables behind the scenes. Because I’m trying to join the users and teams table, I decided to name this model membership.

Running rails g model membership user_id:integer team_id:integer will create a model and migration for the new membership model.

Now, add lines 2 and 3 to set up the relationships to the user and team models.

membership.rb model (after)

class Membership < ActiveRecord::Base
   belongs_to :user
   belongs_to :team
end

The membership model with the relationships above will represent another table that will be used for storing mappings between the user and team models. For example, the table may include data such as:

Sample membership table data

user_id team_id
1 1
2 1
3 1
4 1
1 2
1 3
2 2

This sample data shows how the model can be used to look up many to many relationships.

  • User 1 has many teams 1, 2, and 3
  • Team 1 has many users 1, 2, 3, and 4

Meow, we can associate both the user and team models to have many memberships. In addition, we can use a has_many override to tell active record that “a user has_many teams through the memberships model” and “a team has_many users through the memberships model”.

user.rb (after)

class User < ActiveRecord::Base
  has_many :memberships, :dependent => :destroy
  has_many :teams, through: :memberships
end

team.rb (after)

class Team < ActiveRecord::Base
  has_many :memberships, :dependent => :destroy
  has_many :users, through: :memberships
end

Now that the relationships are set up, assuming that you have ran rake db:migrate to sync the new membership model, each model should be able to take advantage of the relationship.

@team = Team.first
@team.users # array of users

@user = User.first
@user.teams # array of teams