Web Development with Rails and Git Tutorial

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

  1. ▶️ Review an updated class diagram, giving special attention to the new parent class and the new association that have been added.
  2. ▶️ 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.
  3. ▶️ 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.
  4. ▶️ 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).

  1. ▶️ 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.
  2. ▶️ Create an instance of the child model class using new. Note that it is invalid without a associated instance of the parent model class.
  3. ▶️ Add the child model instance to the collection of the parent model instance using <<. Note that nothing has been saved to the database yet.
  4. ▶️ Add a new child model instance to the parent’s collection using build. Note that nothing has been saved to the database yet.
  5. ▶️ Save the parent model instance. Note that all its children were also saved, and all the saves took place as a single transaction.
  6. ▶️ Add a new child model instance to the parent’s collection using create. Note that this time the instance was saved.
  7. ▶️ Iterate through all child model instances in the collection, printing attributes of each.
  8. ▶️ Retrieve a child model instance from the parent’s collection by ID using find.
  9. ▶️ Randomly sample a child model instance from the parent’s collection.
  10. ▶️ 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.
  11. ▶️ 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

  1. ▶️ Add/update test fixtures to use the association.
  2. ▶️ Add/update test case to use the association.
  3. ▶️ 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

  1. ▶️ Add more seed data to db/seeds.rb such that the data now contain uses of the association.
  2. ▶️ Reset and reseed the database (see rails db:reset).
  3. ▶️ Test and debug: Use the Rails console to confirm that the seed data was added correctly.

▶️ Check-in Changes: changeset, snapshot

▶️ 4. Extending the Interactive Form to Use a Has-Many Associations

  1. ▶️ Prep: Review a wireframe diagram of the features we aim to build.
  2. ▶️ Create a controller for the parent model class (see rails generate controller ...).
  3. ▶️ 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.
  4. ▶️ 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.
  5. ▶️ 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

  1. ▶️ Prep: Review a wireframe diagram of the features we aim to build.

▶️ 5.1. Adding CRUD Pages That Handle Only the Parent Table Data

  1. ▶️ 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.
  2. ▶️ 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.
  3. ▶️ 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.
  4. ▶️ 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.
  5. ▶️ Add a create controller action and POST route to process new form submissions. Test and debug as necessary.
  6. ▶️ Add an update controller action and PUT/PATCH routes to process edit form submissions. Test and debug as necessary.
  7. ▶️ 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

  1. ▶️ 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.
  2. ▶️ Update the parent’s show page to include a table of associated child records. Test and debug as necessary.
  3. ▶️ 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.
  4. ▶️ 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.
  5. ▶️ 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.
  6. ▶️ 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.
  7. ▶️ 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.
  8. ▶️ 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

© Scott D. Fleming 2018 • Made with GitHub Pages and Markdown