Tutorial
Step 7: Many to Many
Let’s add a Team relationship: a Team can have many Employees, an
Employee can have many Teams. 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:migrateGraphiti supports has_many :through:
# app/models/employee.rb
has_many :team_memberships
has_many :teams, through: :team_memberships# app/models/department.rb
has_many :teamsclass Team < ApplicationRecord
  belongs_to :department
  has_many :team_memberships
  has_many :employees, through: :team_memberships
endclass TeamMembership < ApplicationRecord
  belongs_to :team
  belongs_to :employee
endFinally, 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
endThe Graphiti Stuff 🎨
$ bin/rails g graphiti:resource Team name:stringLet’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
endThe trick here is the many_to_many relationship. Let’s add the reverse
as well:
# app/resources/employee_resource.rb
many_to_many :teamsAnd for good measure:
# app/resources/department_resource.rb
has_many :teamsWe 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.
