Demo 12: Creating One-to-Many Model Associations
▶️ Play from the beginning (~2 hours, 40 minutes)
In this demonstration, I will explain how to create associations between model classes in Rails and how to perform common tasks using associations. In particular, I will focus on one-to-many associations implemented using Rails has_many
/belongs_to
declarations.
First, a little terminology: I will refer to the model class that has many of the other model class as the parent class, and I will refer to the model class that belongs to the parent as the child class.
▶️ 1. Adding a Has-Many Association to the Model
▶️ 1.1. Setting Up the Association
- ▶️ Review an updated class diagram, giving special attention to the new parent class and the new association that have been added.
- ▶️ Create a new parent model class that will have many of the existing model class using
rails generate model ...
. Note that no foreign keys have been added yet. Apply the new migration (see rails db:migrate
), and review the new model class and the migration.
- ▶️ Add a foreign key column to the child model class using
rails generate migration ...
. Apply the new migration (see rails db:migrate
), and review the child model class and the migration.
- ▶️ Add a
has_many
declaration to the parent model class, and add a belongs_to
declaration to the child model class. Give one of the association ends a custom (non-default) name. Use the class_name
option to explicitly declare the classes on either side of the association. Use the foreign_key
option to explicitly declare the name of the foreign key column. Use the inverse_of
option to explicitly declare the association as bi-directional. Use the dependent: :destroy
option so that when an instance of the parent model class is destroyed all its associated child instances will also be destroyed. (See the Rails Guides on Active Record Associations for documentation.)
▶️ 1.2. Touring the Functionality
For this part, we will use the rails console (rails console
to start and exit
to end).
- ▶️ Create an instance of the parent model class using
new
. Note that it has an empty collection of child model objects. Also note that it is valid.
- ▶️ Create an instance of the child model class using
new
. Note that it is invalid without a associated instance of the parent model class.
- ▶️ Add the child model instance to the collection of the parent model instance using
<<
. Note that nothing has been saved to the database yet.
- ▶️ Add a new child model instance to the parent’s collection using
build
. Note that nothing has been saved to the database yet.
- ▶️ Save the parent model instance. Note that all its children were also saved, and all the saves took place as a single transaction.
- ▶️ Add a new child model instance to the parent’s collection using
create
. Note that this time the instance was saved.
- ▶️ Iterate through all child model instances in the collection, printing attributes of each.
- ▶️ Retrieve a child model instance from the parent’s collection by ID using
find
.
- ▶️ Randomly sample a child model instance from the parent’s collection.
- ▶️ Destroy the sampled child model instance using the
destroy
method of the parent’s collection, removing it from the parent’s collection in the process.
- ▶️ Destroy the parent model instance using the parent’s
destroy
method. Note that all child model instances in its collection were also destroyed.
▶️ Check-in Changes: changeset, snapshot
▶️ 2. Adding Model Tests and Fixtures That Use Associations
- ▶️ Add/update test fixtures to use the association.
- ▶️ Add/update test case to use the association.
- ▶️ Test and debug: Run to tests to confirm that they work (see
rails test
).
▶️ Check-in Changes: changeset, snapshot
▶️ 3. Adding Seed Data That Use Associations
- ▶️ Add more seed data to
db/seeds.rb
such that the data now contain uses of the association.
- ▶️ Reset and reseed the database (see
rails db:reset
).
- ▶️ Test and debug: Use the Rails console to confirm that the seed data was added correctly.
▶️ Check-in Changes: changeset, snapshot
- ▶️ Prep: Review a wireframe diagram of the features we aim to build.
- ▶️ Create a controller for the parent model class (see
rails generate controller ...
).
- ▶️ Add a home page for the parent class that links to the existing child class’s interactive form. Add a route, a controller action, and a view. Test and debug as necessary.
- Oops fixed! Revised CSS block style on
label
and input
elements so they look better for radio buttons.
- ▶️ Update the child’s interactive form to redirect back to the parent model’s home page. Test and debug as necessary.
- Oops fixed! Corrected the alert message in the controller action that processes interactive form submissions.
- ▶️ Update the app’s home page to list all records of the parent model database with hyperlinks to each record-specific home page. Test and debug as necessary.
- Oops fixed! Corrected the view code for radio options to not display options for nil fields in the model.
▶️ Check-in Changes: changeset, snapshot
▶️ 5. Adding CRUD Pages Parent Model Records
- ▶️ Prep: Review a wireframe diagram of the features we aim to build.
▶️ 5.1. Adding CRUD Pages That Handle Only the Parent Table Data
- ▶️ Add an index page for parent model records, including a route, a controller action, and a view. Update the nav bar in
application.html.erb
to link to this page. Test and debug as necessary.
- ▶️ Add a show page for parent model records, including a route, a controller action, a view, and links on the index page. Test and debug as necessary.
- ▶️ Add a new form for parent model records, including a route, a controller action, a form partial, a view, and a link on the index page. Test and debug as necessary.
- ▶️ Add an edit form for parent model records, including a route, a controller action (that uses the already-created form partial), a view, and links on the index and show pages. Test and debug as necessary.
- ▶️ Add a create controller action and POST route to process new form submissions. Test and debug as necessary.
- ▶️ Add an update controller action and PUT/PATCH routes to process edit form submissions. Test and debug as necessary.
- ▶️ Add a destroy controller action, including a DELETE route and links on the index page. Test and debug as necessary.
▶️ Check-in Changes: changeset, snapshot
▶️ 5.2. Integrating the Child CRUD Pages with the Parent CRUD Pages
- ▶️ Comment out all of the child’s CRUD-page routes and controller actions. We will restore and modify them as we integrate them with the parent pages.
- ▶️ Update the parent’s show page to include a table of associated child records. Test and debug as necessary.
- ▶️ Update the parent’s edit form to include an index-page-style listing of the associated child objects. The table will eventually include create, edit, and destroy links for the child records; however, omit these links for now, and we will add them in as we add their associated functionality. Test and debug as necessary.
- ▶️ Update the child’s new form to take into account a parent’s ID. This change will involve the following. Routes: Add a route for the child’s
new
action that includes a parent ID in the URL pattern. Restore the route for the child’s create
action. Add to the create route the prefix from the now-removed index route. Controller: Restore the new
controller action and update it to retrieve the parent ID from the params
hash and pass it to the view. Views: Update the child’s new.html.erb
view to pass the parent ID to the form partial. Update the form partial to include a hidden field (see hidden_field_tag
) containing the parent ID. Add a link to the new page on the parent edit page. Test and debug as necessary.
- ▶️ Update the child’s
create
action to make use of the parent’s ID. This change will involve following. Restore the create
method and update it to retrieve from the params
hash the parent ID from the hidden field. Retrieve the parent using the ID. Add the child to the parent’s collection. Update HTTP redirects as appropriate. Test and debug as necessary.
- ▶️ Update the child’s edit page to take into account a parent’s ID. This change will involve following. Routes: Restore the route for the child’s
edit
action and the route for the child’s update
action. Add to the update route the prefix from the now-removed show route. Controller: Restore the edit
route. Views: Update the edit.html.erb
view to retrieve the parent ID from the child object and pass the ID to the form partial. Add links to edit pages to the parent update page. Test and debug as necessary.
- ▶️ Update the child’s
update
action to make use of the parent’s ID. This change should mainly involve the following. Restore the update
action, updating the HTTP redirects as appropriate. Test and debug as necessary.
- ▶️ Update the child’s destroy action to take into account the parent’s ID. This change will involve the following. Routes: Restore the route for the child’s
destroy
action. Controller: Restore the destroy
action, updating the HTTP redirects as appropriate. Views: Add destroy links on the parent edit page. Test and debug as necessary.
▶️ Check-in Changes: changeset, snapshot