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_boxes
form 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_many
Rangers - Ranger
has_and_belongs_to_many
Parks
- 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_ids
refers to the model's rangers@rangers
refers to all the rangers available (pass from the controllerRanger.all
):id
refers to the value of the checkbox:name
refers 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.