Rails N:M
Many to Many Associations
Objectives
Implement many to many relationships through models in Rails
Describe the model ordering opinion used by Rails
Use the
collection_check_boxesform helper to display a collection of associated items
Today we'll add rangers to the national park app using a many to many relationship.
Review: Relationships

What we need
Models
Park
Ranger
ParksRangers (join table)
Association
Park <-> ParksRangers <-> Ranger
Park
has_and_belongs_to_manyRangersRanger
has_and_belongs_to_manyParks
Views
parks#edit - add/remove rangers checkboxes
parks#new - add/remove rangers checkboxes
rangers#show
list all parks with a specific ranger
Generating models
Review of Parks
rails g model park name description:text picture:textRangers
rails g model ranger nameParksRangers
rails g model parks_rangers park:references ranger:references --force-pluralIMPORTANT
The join table with the two models must be plural and in alphabetical order if you want to follow the Rails convention. Also, --force-plural is needed so that the table will never be pluralized.
Note that if you want to name your join table something different, you can specify your own join model with through:
Setting up associations
When you do :references it automatically creates the belongs_to relations on the join table, but we need to manually add the has_and_belongs_to_many to the ranger and park models.
models/park.rb
has_and_belongs_to_many :rangersmodels/ranger.rb
has_and_belongs_to_many :parksALSO IMPORTANT
When creating the M:M associations, the name of the model is pluralized when adding the has_and_belongs_to_many method. In ParksRangers, the associations will be singular and generated for you.
Adding rangers
# assume the following:
park = Park.first
ranger = Ranger.first
# adding a ranger
park.rangers << rangerRemoving rangers
# assume the following:
park = Park.first
ranger = Ranger.first
# clear all of the park's rangers (leaves the rangers in the table)
park.rangers.clear
# removes a specific ranger from a park (leaves the ranger in the table)
c.rangers.delete(ranger)
# removes a specific ranger from a park (and deletes the ranger)
c.rangers.first.destroyReferencing and listing
Because Park and Ranger reference each other with has_and_belongs_to_many they can reference each other.
Basic Examples
#lists all rangers
Ranger.all
#lists all parks
Park.all
#gets first park in the database
Park.first
#lists all rangers of first park
Park.first.rangers
#lists all parks of first ranger
Ranger.first.parksAdvanced Examples / chaining
#All parks of the first ranger of the first park
Park.first.rangers.first.parksparks#new and parks#edit
Add checkboxes to the form
<%= c.collection_check_boxes :ranger_ids, @rangers, :id, :name %>:ranger_idsrefers to the model's rangers@rangersrefers to all the rangers available (pass from the controllerRanger.all):idrefers to the value of the checkbox:namerefers to the label of the checkbox
That's it! As far as assigning the rangers in the controller, we can modify the Park model to accept the ranger_ids array like so:
def park_params
params.require(:park).permit(:name, :description, :ranger_ids => [])
endIn order for the rangers to be assigned automatically, we can add the accepts_nested_attributes_for method to the Park model. You'll also want to add inverse_of: to the park's ranger association, in order to run any validations that may be on the Ranger model.
class Park < ActiveRecord::Base
has_and_belongs_to_many :rangers, inverse_of: :park
accepts_nested_attributes_for :rangers
endrangers#show
When showing a specific ranger, display the ranger name and the list of parks associated with it.
Last updated
Was this helpful?