Routes: Show Page

Exercises

Objective

As our app now stands, we have one page that lists all of the movies in the database. When we visit /movies, the index action runs and fetches all the movies from the database. Then it renders the index.html.erb template which generates an HTML list of movies and sends it back to the browser. That makes for a nice summary page.

In this exercise we'll create the second page of our app. When we visit /movies/1, for example, we want to see the details for that particular movie. By convention, Rails calls this the "show" page. To make that work, we need to do three things:

  1. Add a generic route to handle requests for /movies/1, /movies/2, /movies/3, and so on.

  2. Define a show action in the MoviesController that finds the movie with the id specified in the URL.

  3. Create a show.html.erb template that generates HTML to display the movie's details.

If you're new to working with MVC, keeping everything in your head can be tricky. So, here's a visual of our objective:

1. Show One Movie

Again, we'll work through this from the outside in, using the error messages to light our path forward.

  1. Start by browsing to http://localhost:3000/movies/1 and you should get the following error:

    Routing Error
    
    No route matches [GET] "/movies/1"
  2. Remember how to resolve this error? Add a route to the config/routes.rb file that maps GET requests for movies/1 to the show action of the movies controller. Don't worry about supporting other movie ids for now. We'll follow the literal error and work our way back to a more generic solution once we have the basic flow down.

    Show Answer

    Hide Answer

    Rails.application.routes.draw
      get "movies"   => "movies#index"
      get "movies/1" => "movies#show"
    end
    
  3. Refresh your browser (you're still accessing http://localhost:3000/movies/1) and this time you should get a different error message:

    Unknown action
    
    The action 'show' could not be found for MoviesController
  4. That's your cue—and you know exactly what to do next! Define the show action such that it finds the movie in the database that has a primary key (id) of 1 and assigns it to an instance variable named @movie.

    Show Answer

    Hide Answer

    def show
      @movie = Movie.find(1)
    end
    
  5. Refresh your browser again and—you probably anticipated it— we're missing something:

    No template for interactive request
    
    MoviesController#show is missing a template...

    Remember, unless we tell it otherwise, after running an action Rails will always try to find a view template using a naming convention. In this case, the name of the action is show, and it's in the MoviesController class, so Rails tries to render the app/views/movies/show.html.erb view template file.

  6. Create a file named show.html.erb in the app/views/movies directory. Inside that file, start by simply displaying the movie's title to get confidence that the correct movie is being fetched from the database.

    Show Answer

    Hide Answer

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

    Refresh and you should see the movie title! That tells us we have the model, view, and controller happily connected.

  7. Now update the show.html.erb template to display all the movie's information. Keep it simple by putting each attribute in a paragraph tag. In terms of helper methods, we don't want to truncate the movie description on this page. However, on this page we do want to display the movie rating and year it was released. (Remember, we removed those movie attributes from the movie listing page.) Since we went to the trouble of creating the total_gross and year_of view helpers in a previous exercise, we might as well call those helpers here.

  8. Once you get a basic show page working, go ahead and copy in the version in the answer. It uses HTML elements and class names that trigger the styles in our custom.scss stylesheet.

    Show Answer

    Hide Answer

    <section class="movie-details">
      <div class="details">
        <h1><%= @movie.title %></h1>
        <h2>
          <%= year_of(@movie) %> &bull; <%= @movie.rating %>
        </h2>
        <p>
          <%= @movie.description %>
        </p>
        <table>
          <tr>
            <th>Total Gross:</th>
            <td><%= total_gross(@movie) %></td>
          </tr>
        </table>
      </div>
    </section>
    

That's a great start!

2. Show Any Movie

Now that we have the MVC flow working, let's make this more generic.

  1. Browse to http://localhost:3000/movies/2 and you should get the following error:

    Routing Error
    
    No route matches [GET] "/movies/2"
  2. We knew that would happen because we only added a route for movies/1, and that route doesn't match this request. At this point we could add another route to handle movies/2, but clearly we need to make this more dynamic. The route needs to support a variable number of movie ids.

    Update the route to match requests for showing any movie.

    You need to replace the hard-coded "1" in the route with a placeholder, or variable, that represents the movie's id. Variables in routes start with a colon (:). In this case, you want a variable named :id.

    Rails.application.routes.draw
      get "movies"     => "movies#index"
      get "movies/:id" => "movies#show"
    end
    
  3. Now you should be able to browse to any of these URLs without getting any errors:

  4. There's just one problem: All of those pages show the details for the first movie! Fix that by updating the show action to use the number at the end of the URL to find the movie in the database.

    When the route contains a colon (:) followed by a name, think of it like a placeholder that gets filled in with whatever shows up at that place in the requested URL. For example, given the route "movies/:id", a request for /movies/7 will fill in the :id placeholder with the value 7. And you can get access to that value using params[:id] in the show action. The params hash includes all URL request parameters. And you can access this hash inside of any controller action.

    def show
      @movie = Movie.find(params[:id])
    end
    
  5. Now try visiting all three URLs above and you should see the matching movie's details.

Great—now we have our "show" page implemented!

Solution

The full solution for this exercise is in the show-page directory of the code bundle.

Wrap Up

This exercise was a good opportunity to take a round trip back through the entire MVC cycle. Now we have two different paths through our application: /movies shows all the movies and /movies/:id shows the details of any single movie. You probably noticed that adding the second path involved the same high-level steps as the first:

  • add a route

  • define an action

  • create a template

You'll end up following those same three steps over and over again as you develop Rails apps. It's the recipe for accepting requests and generating responses. The details vary depending on how you want the request to be handled, and we'll see more examples of that in future exercises, but you can flesh out a basic flow simply by following the errors as we've done here.

Hey, now that we have two pages, it sure would be nice if we could easily navigate between them! Typing these URLs in the browser's address bar is getting kinda tedious. So in the next section we'll generate hyperlinks so we can easily navigate between pages.