Tutorial
Step 7: Many to Many
Let’s add a Team
relationship: a Team
can have many Employee
s, an
Employee
can have many Team
s. Let’s also say a Team
belongs to a
Department
.
id | department_id | name |
---|---|---|
1 | 1 | The A Team |
2 | 1 | The B Team |
3 | 2 | The C Team |
To satisfy this many-to-many use case, we’ll need a join model,
TeamMembership
:
id | team_id | employee_id |
---|---|---|
1 | 1 | 1 |
2 | 2 | 1 |
3 | 3 | 2 |
The Rails Stuff 🚂
$ bin/rails g model Team name:string department:belongs_to
$ bin/rails g model TeamMembership employee:belongs_to team:belongs_to
$ bin/rails db:migrate
Graphiti supports has_many :through
:
# app/models/employee.rb
has_many :team_memberships
has_many :teams, through: :team_memberships
# app/models/department.rb
has_many :teams
class Team < ApplicationRecord
belongs_to :department
has_many :team_memberships
has_many :employees, through: :team_memberships
end
class TeamMembership < ApplicationRecord
belongs_to :team
belongs_to :employee
end
Finally, we’ll need a new seed file to handle these new associations:
[
Employee,
Position,
Department,
TeamMembership,
Team
].each(&:delete_all)
departments = []
def create_department(name)
dept = Department.create! name: name
dept.teams.create!(name: 'Engineering Team B')
dept.teams.create!(name: 'Engineering Team C')
dept
end
departments << create_department('Engineering')
departments << create_department('Safety')
departments << create_department('QA')
100.times do
employee = Employee.create! first_name: Faker::Name.first_name,
last_name: Faker::Name.last_name,
age: rand(20..80)
(1..2).each do |i|
employee.positions.create! title: Faker::Job.title,
historical_index: i,
active: i == 1,
department: departments.sample
end
employee.teams << employee.positions[0].department.teams.sample
end
The Graphiti Stuff 🎨
$ bin/rails g graphiti:resource Team name:string
Let’s flesh out our TeamResource
:
# app/resources/team_resource.rb
class TeamResource < ApplicationResource
attribute :department_id, :integer, only: [:filterable]
attribute :name, :string
belongs_to :department
many_to_many :employees
end
The trick here is the many_to_many
relationship. Let’s add the reverse
as well:
# app/resources/employee_resource.rb
many_to_many :teams
And for good measure:
# app/resources/department_resource.rb
has_many :teams
We can now get all the usual functionality: fetch Employees and their Teams in a single request (or vice versa).
Digging Deeper 🧐
The many_to_many
relationship is the only one where Graphiti modifies
a separate Resource “under the hood”. When we said many_to_many
:employees
, the EmployeeResource
got a team_id
filter, and
many_to_many :teams
created an employee_id
filter on TeamResource
.
This is because the logic is more complex than the default use case. We
don’t have a simple WHERE
clause; we need to join tables and look at
the appropriate primary/foreign keys. If the name of your API
association doesn’t match the name of your ActiveRecord association, try
has_many :things, as: :my_activerecord_relationship
to make the
introspection work correctly - or, write your own filter.
Sometimes you’ll have multiple levels of has_many :through
. In this
case, a simple many_to_many
isn’t enough - check out our Hopping
Relationships Cookbook.
Think hard before reaching for many_to_many
. Imagine one Team is the
“primary” Team for an Employee. We’d add a primary
boolean column to
the team_memberships
table…but that table isn’t exposed to the API!
Consider if there’s a hidden domain concept there.