Many to Many Associations
Objectives
- Implement many to many relationships through models in Rails
- Understand 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_manyRangers - Ranger
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:text
Rangers
rails g model ranger name
ParksRangers
rails g model parks_rangers park:references ranger:references --force-plural
IMPORTANT
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 :rangers
models/ranger.rb
has_and_belongs_to_many :parks
ALSO 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:
some_park = Park.first
some_ranger = Ranger.first
# adding a ranger
some_park.rangers << some_ranger
Removing rangers
# assume the following:
some_park = Park.first
some_ranger = Ranger.first
# clear all of the park's rangers (leaves the rangers in the table)
some_park.rangers.clear
# removes a specific ranger from a park (leaves the ranger in the table)
some_park.rangers.delete(some_ranger)
# removes a specific ranger from a park (and deletes the ranger)
some_park.rangers.first.destroy
Referencing 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.parks
Advanced Examples / chaining
#All parks of the first ranger of the first park
Park.first.rangers.first.parks
parks#new and parks#edit
Add checkboxes to the form
<%= f.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 controller to accept the ranger_ids array like so:
def park_params
params.require(:park).permit(:name, :description, :ranger_ids => [])
end
rangers#show
When showing a specific ranger, display the ranger name and the list of parks associated with it.