Partials

Exercises

Objective

Crafting good Rails apps isn't just about implementing features that do what they should do. Good Rails apps also have clean, well-organized code under the hood. Indeed, what you see on the inside is as important as what you see on the outside. And that's because an online web app is never really done. It's inevitable that you'll need to add new features, be it tomorrow or six months down the road. And when that time comes, you want your app to be in a position where you can make the changes as quickly and efficiently as possible.

So let's take stock of where we left off in the last exercise. We can now edit and create movies. We even removed a bit of duplication in the update and create actions so that the controller code is easier to change later. But what about the edit and new forms themselves? We took a deliberate shortcut when we blatantly copied the form in the edit.html.erb template and pasted it into the new.html.erb template. We did that to quickly get up and running and learn how forms work. But duplicated code anywhere in an application, even in a view template, is a liability. If we need to change the form to accommodate a future feature, we'll need to make the change in two places. And that's both prone to error (we'll likely forget to make the change in both places) and also double the work.

So before we strike a big red line through these features and move on, let's take a minute to clean up the duplication in the forms. We'll also restructure our layout file so it's better organized.

1. Remove Duplication Using a Partial

As it stands, the edit.html.erb and new.html.erb templates both have the exact same form code. We want the form code in one place, so we'll put the form in a common partial file. Think of a partial as a reusable view "chunk" (yes, that's the technical term) that you can render from other views. Once we have the form in a partial, we'll render it from the edit.html.erb and new.html.erb templates. It's two forms for the price of one!

  1. Start by creating a file named _form.html.erb (partial files always start with an underscore) in the app/views/movies directory.

  2. Then go into the edit.html.erb template and cut all the code from form_with to end and paste it into the _form.html.erb file.

    Show Answer

    Hide Answer

    <%= form_with(model: @movie, local: true) do |f| %>
      <%= f.label :title %>
      <%= f.text_field :title %>
    
      <%= f.label :description %>
      <%= f.text_area :description, rows: 7 %>
    
      <%= f.label :rating %>
      <%= f.text_field :rating %>
    
      <%= f.label :released_on %>
      <%= f.date_select :released_on, {}, {class: "date"} %>
    
      <%= f.label :total_gross %>
      <%= f.number_field :total_gross %>
    
      <%= f.submit %>
    <% end %>
    
  3. Then, back in the edit.html.erb template, render the _form partial.

    Remember that even though the partial file is prefixed with an underscore, you don't use the underscore when calling render.

    <h1>Editing Movie: <%= @movie.title %></h1>
    
    <%= render "form" %>
    
  4. Now browse to http://localhost:3000/movies/1/edit and you shouldn't see any evidence that we shuffled code around. The form should appear exactly as it did before. One template down, one to go!

  5. Next, go into the new.html.erb template and replace the inline form code with the result of rendering our fancy new form partial.

    Show Answer

    Hide Answer

    <h1>Create a New Movie</h1>
    
    <%= render "form" %>
    
  6. As a sanity check, browse to http://localhost:3000/movies/new and again the form should appear exactly as before.

It's like we were never here...

2. Use a Local Variable

Currently the _form partial depends on an @movie instance variable. This certainly works, but it means the partial is tightly coupled to the controller actions that render it, which in this case is the new and edit actions.

Rather than using instance variables in partials, it's considered a best practice to instead explicitly pass the partial the data it needs using local variables. That way the partial isn't dependent on something outside of its scope, it just relies on local variables that were passed to it. And that makes the partial easier to reuse and maintain over time.

So let's rearrange things slightly so the _form partial uses a local variable named movie.

  1. First, in the _form.html.erb partial, change the form_with to use a local movie variable rather than a @movie instance variable.

    Show Answer

    Hide Answer

    <%= form_with(model: movie, local: true) do |f| %>
      # existing fields
    <% end %>
    
  2. Then in the new.html.erb template where the form partial is rendered, pass it a local variable named movie that has the value of the @movie instance variable.

    Show Answer

    Hide Answer

    <h1>Create a New Movie</h1>
    
    <%= render "form", movie: @movie %>
    
  3. And do the same in the edit.html.erb template where it renders the form partial.

    Show Answer

    Hide Answer

    <h1>Editing <%= @movie.title %></h1>
    
    <%= render "form", movie: @movie %>
    

It might help to think of partials as being like simple functions. Functions get passed arguments, and those arguments are scoped to the function as local variables. In general, functions should avoid relying on global variables that are outside the scope of a function. In the same way, partials should be passed local variables that are then scoped to the partial rather than relying on instance variables that are outside a partial's scope.

3. Accommodate New Features

Here comes the big payoff! Suppose some bright spark decides that all the movie forms should auto-focus the title field. And while we're at it, the description field should have 10 rows instead of 7 rows. The person requesting these features would like an estimate of how long all this will take on his desk by tomorrow morning, preferably hand-signed. What he doesn't know is that we're way ahead of the game now.

  1. Change the shared _form.html.erb partial to autofocus the title field.

    Show Answer

    Hide Answer

    <%= form_with(model: movie, local: true) do |f| %>
      <%= f.label :title %>
      <%= f.text_field :title, autofocus: true %>
    
      <%= f.label :description %>
      <%= f.text_area :description, rows: 10 %>
    
      <%= f.label :rating %>
      <%= f.text_field :rating %>
    
      <%= f.label :released_on %>
      <%= f.date_select :released_on, {}, {class: "date"} %>
    
      <%= f.label :total_gross %>
      <%= f.number_field :total_gross %>
    
      <%= f.submit %>
    <% end %>
    
  2. Browse to http://localhost:3000/movies/1/edit and http://localhost:3000/movies/new and the title field should automatically have focus and the description field should have 10 rows.

  3. Finally, send the person who requested these changes a one-word email: "Done!"

4. Extract Header and Footer Into Partials

We've seen that partials offer an easy way to remove view-level duplication. You can also use partials to decompose a view template into small chunks for better readability and maintenance.

For example, the header and footer sections of a layout can become fairly substantial as an application grows. And as things start to accumulate, the layout file can morph into an unrecognizable jumble of HTML and ERb tags. To help keep that under control, it's generally a good practice to extract sections of views (including layouts) that logically go together into partials.

So let's do ourselves a favor now by decomposing the layout file into separate header and footer partials. When we're done, you won't see any visible changes on the site. But you'll feel better knowing the internal design is cleaner and easier to maintain.

  1. Create a file named _header.html.erb in the app/views/layouts directory. Copy the header section from the application.html.erb layout file and paste it into the new _header.html.erb partial.

    Show Answer

    Hide Answer

    <header>
      <nav>
        <%= link_to image_tag("logo"), root_path %>
        <ul>
          <li>
            <%= link_to "All Movies", movies_path %>
          </li>
        </ul>
      </nav>
    </header>
    
  2. Then, back in the layout file, replace the header section with a call to render the layouts/header partial.

    By default, render looks for the partial in the same directory as the current view template being rendered. If there’s a slash (/) in the partial name, render treats the first part as the directory name and the second part as the file name. Remember that you'll need to use the ERb tag <%= %> (with an equal sign) in order for the rendered partial to get substituted back into the layout file.

    <%= render "layouts/header" %>
    
  3. Getting the hang of this? How about trying the footer on your own.

    Create a file named _footer.html.erb in the app/views/layouts directory. Copy the footer section from the application.html.erb layout file and paste it into the new _footer.html.erb partial.

    # in _footer.html.erb
    
    <footer>
      <p>
        Copyright &copy; 2005-<%= Time.now.year %>
          <%= link_to 'The Pragmatic Studio','https://pragmaticstudio.com' %>
      </p>
    </footer>
    
    # in application.html.erb
    
    <%= render "layouts/footer" %>
    
  4. Finally, check your work by refreshing the browser. The app should look exactly as it did before, but now we've organized things better into partials. If you run into trouble, double check that your layout file matches up with the version shown in the answer.

    Show Answer

    Hide Answer

    <%= render "layouts/header" %>
    
    <div class="content">
      <%= yield %>
    </div>
    
    <%= render "layouts/footer" %>
    

Solution

The full solution for this exercise is in the partials directory of the code bundle.

Wrap Up

That's really all there is to partials. Most often you'll use them to remove duplication in views so you can make changes in one place. And in the same way you'd break a big Ruby method into smaller methods for better readability, you can use partials to break big view templates into smaller (reusable) view chunks.

A bit later we'll see more examples of where partials come in handy. But first, in the next section we'll implement the last remaining route: deleting a movie.

Dive Deeper

To learn more about partials, refer to the Rails Guides: Using Partials.