Show Pages Displaying Individual Model Objects

In this demonstration, I will show how to create a “show” pages for displaying individual model objects retrieved from the database.

General Steps

In general, the steps for creating show pages are as follows.

Creating a Show Page for Individual To-Do Items

To demonstrate the steps for creating show pages, we will be creating show pages for a model class representing items in a to-do list. Figure 1 illustrates how a show page will look for a particular to-do item.

A webpage for a to-do item, including a title for the item, a due date, and a description

Figure 1. A show page displaying an individual item in a to-do list.

For this demo, we will continue to build upon the app from the index pages demo. Recall that the app contains a Todo model class (depicted in Figure 2), a seeds script for populating the database with some sample Todo objects, a TodosController class, and a route, a controller action, and a view template for the index page.

A class diagram depicting a Todo model class with the following attributes: a title string, description text, and a due date

Figure 2. The Todo model class.

In addition to implementing show pages for the Todo objects, we will also add links from the index page to the show pages, as illustrated in Figure 3.

A single record and its details

Figure 3. The index page for the to-do list app, now with a link from each to-do in the table to the corresponding show page.

Base App Code

Step 1 Add a Show Route

To add a route for the show pages, we add a route declaration to the config/routes.rb file, like this:

Rails.application.routes.draw do

  get 'todos', to: 'todos#index', as: 'todos'
  get 'todos/:id', to: 'todos#show', as: 'todo'

end

Note that, unlike the routes we’ve previously created, the resource path for the show route ('todos/:id') has a parameter (:id) for specifying the ID of the model object to be displayed. For example, this route will match on requests for http://localhost:3000/todos/1, http://localhost:3000/todos/2, etc. When the controller action processes one of these requests, it will be able to retrieve the ID number from the URL, so it knows which model object to display.

Also, note that this route adheres to the following Rails resourceful-style naming conventions. The first part of the resource path (todos/) and the controller name are both named like the model database table—that is, the model name in snake_case and pluralized (i.e., todos). Don’t forget that the name for the controller specified in the route (todos) is a shorthand for the actual controller class name (TodosController). The name of the controller action method is show. The path/URL helper prefix is the snake_case version of the model class name (i.e., todo). Note that the path/URL helper prefix for the show route is singular (because show corresponds to a single model object), unlike the index route, which is plural (because index corresponds to multiple model objects).

Test It!

To confirm that we made this change correctly, we run the following command in the terminal:

rails routes -E

The command should output a long list of routes, but the very first one is the only one we care about. If we coded our route correctly, it should look like this:

--[ Route 2 ]-------------------------------------------------------------------------------------------------------------------
Prefix            | todo
Verb              | GET
URI               | /todos/:id(.:format)
Controller#Action | todos#show

If we made an error, something different will be outputted. For example, if we made a syntax error, it would instead produce an error message.

Step 1 Changeset

Step 2 Create a Model Controller (if Needed)

Because we previously generated the TodosController, we skip this step.

Step 3 Create a Show Controller Action

To create the controller action for the show pages, we perform two substeps: first, we create the controller action code for rendering the view, and then, we add the code for retrieving the model object that will be displayed in the view.

Substep Create a show controller action that renders the appropriate view. To complete this substep, we define an show method in the TodosController class (in app/controllers/todos_controller.rb), like this:

class TodosController < ApplicationController

  def index
    @todos = Todo.order(:due_date)
    render :index
  end

  def show
    render :show
  end

end

Note that, so far, the show controller action only renders the view template app/views/todos/show.html.erb (as specified by the :show symbol).

Substep Make the controller action retrieve the model object to be displayed in the view. To complete this substep, we add a call to the Todo.find model class method to the show method of the TodosController class, like this:

  def show
    @todo = Todo.find(params[:id])
    render :show
  end

The find model class method is used to retrieve a model object from the database by id. The above call to Todo.find returns a Todo object (i.e., the one retrieved from the database), and the @todo instance variable is assigned a reference to that object. The use of an instance variable (indicated by the @ in @todo) is important here, because instance variables are accessible within view templates rendered by the class, thus allowing data to be passed from the controller to the view. The params method is provided by Rails as a way to access the all the data received in the HTTP request to be processed by the controller action. Although params is technically a method, it can be used like a hash. The above call to params[:id] returns the ID embedded in the resource path of the HTTP request (recall the 'todos/:id' part of the show route). The ID is, in turn, passed to the find call, effectively telling the call which Todo object to retrieve from the database.

Test It!

To at least partially confirm that we made this change correctly, we run the web app (as per the steps in the running apps demo), and we open the URL http://localhost:3000/todos/4 in our web browser. An error page should be displayed with the message “Template is missing”, because we have not yet created a view template for the show page. If a different error message appears (e.g., a syntax error), then we made some other mistake, which we would want to fix before proceeding to the next step.

Step 3 Changeset

Step 4 Create a Show View Template

To print the retrieved Todo object on a page (as shown in Figure 1), we first create a view template containing an HTML skeleton for the page, and then, we add logic that fills in the page with data for the object.

Substep Rough in the HTML skeleton. To complete this substep, we create a new view template file in the folder app/views/todos/ named show.html.erb, and we add HTML elements to the file, like this:

<h1>TITLE_GOES_HERE</h1>

<p>
  Due: DUE_DATE_GOES_HERE
</p>

<p>
  DESCRIPTION_GOES_HERE
</p>

Note that, for now, the page has only “X_GOES_HERE” placeholder text for the to-do item’s data.

Substep Fill in the page with the data for the retrieved model object. To fill in the page the data of the Todo object, we replace the “X_GOES_HERE” placeholder values with embedded Ruby calls to the appropriate Todo attribute accessor methods, like this:

<h1><%= @todo.title %></h1>

<p>
  Due: <%= @todo.due_date.strftime("%m/%d/%Y") %>
</p>

<p>
  <%= @todo.description %>
</p>

In the above view code, the @todo variable comes from the show method above and references the retrieved Todo object. The calls to title, description, and due_date each return the value of the corresponding attribute for the object. The list of Todo class attributes can be found in the comments at the beginning of the model class file, app/models/todo.rb. Surrounding each call to an attribute accessor method in the embedded Ruby tag <%= … %> (note the =) causes the value returned by the method to be written to the generated HTML. Note that the Date method strftime method was used to format the due_date attribute. Here, we passed strftime the argument "%m/%d/%Y", which will format the date as MM/DD/YYYY (e.g., 02/14/2021).

Test It!

To verify that we performed all parts of this step correctly, we run the web app (as per the steps in the running apps demo), and we open the URL http://localhost:3000/todos/4 in our web browser. The webpage displayed should look exactly like Figure 1.

Step 4 Changeset

To add links from the index page to the various show pages, we add code to the index view template (`app/views/todos/index.html.erb) such that the title of each model object is wrapped in a link to the object’s show page, like this:

<h1>To-Do Items</h1>

<table class="table table-hover">

  <thead class="thead-dark">
    <tr>
      <th>Title</th>
      <th>Due Date</th>
    </tr>
  </thead>

  <tbody>
    <% @todos.each do |todo| %>
      <tr>
        <td><%= link_to todo.title, todo_path(todo) %></td>
        <td><%= todo.due_date.strftime("%m/%d/%Y") %></td>
      </tr>
    <% end %>
  </tbody>

</table>

Note that we follow the general steps from the internal links demo to add the link; however, there are a couple of new things to note. For one, we get the link text from a method call (todo.title) rather than a string literal. For another, the show route has a singular path/URL helper prefix ('todo') and a parameterized resource path ('todos/:id'). As a consequence, the show page’s path/URL helper methods must also take a parameter to fill in the ID value. Thus, in the above code, we use the path helper call, todo_path(todo), which passes the current Todo object in as an argument. The todo_path method can tell that its todo argument references a model object, and knows how to get the ID from that object.

Test It!

To verify that we completed this step correctly, we run the web app (as per the steps in the running apps demo), and we open the URL http://localhost:3000/todos in our web browser. The webpage displayed should look exactly like Figure 3. Clicking any link on the page should open the corresponding todo item’s show page.

Step 5 Changeset

Conclusion

Following the above steps, we have now added show pages to the to-do list app.

Demo App Code