learns_to: A None-Too-Brief Introduction to Partials

3 April, 2006

One of the worst parts about writing websites by hand is the repetition. If you've ever tried it, you've probably run into a problem like this: You've got a set of links at the top (and bottom) of each page that act as persistent navigation. So, they should be the same everywhere. But you also want them to help indicate where the reader is currently. So, they need to be different on each page. To illustrate, here's the header from the redesign of the At Dusk website I'm currently working on:

<p>
<a href="blog.html">blog</a>
<a href="bio.html">bio</a>
<a href="music.html" class="selected">music</a>
<a href="live.html">live</a>
<a href="press.html">press</a>
<a href="contact.html">contact</a>
</p>

This is the header for the "music" page. It is identical to that which appears on all the other pages except for the "selected" class on the link to "music.html". In the CSS, I use that class to add a red underline to the link. And, on each different page, the "selected" class appears on the appropriate link, letting the user know where they are.

With hand-written HTML, this quickly becomes a problem. Every time I start a new page, I've got to not only copy in the navigation, but edit it so that the "selected" class is in the right place, and also go back to all of the pages I've already made and edit their navigation to include a link to the new page. Doing this a couple of times, there gets to be little chance of me not forgetting to add a link or change a class.

Now, this seems like exactly the kind of problem that Rails exists to solve. It would be crazy to make it so easy to do really hard tasks like dynamically accessing a database and then end up with a bunch of repeated hand-coded out-of-sync HTML fragments everywhere. Rails enthusiasts treat eliminating repetition like a religious calling so the framework missing an opportunity like this would certainly send them off on a jihad.

And so, of course Rails has a system to handle the repeated parts of your website, whether they be plain HTML or the most complex ruby allowed in view logic. The system uses files called Partials. Partials look just like other views except that their file names start with an underscore.

So, how do we use partials to fix the problem of the context-dependent header? Well, first let's make certain assumptions about how this project would be structured in Rails (crazily enough, it's not. I'm actually hand-coding the new At Dusk site as a temporary measure until we find some Rails hosting). Let's say that each of these pages are views within a single controller called 'info' except the blog which is the index action of it's own controller called 'blog' (this isn't necessarily one hundred percent realistic, but it's enough to let us explore this issues at hand).

Let's make our partial. As the header, it should probably go in the 'layouts' folder, so create a new file there called '_header.rhtml' and in application.rhtml itself we'll add the following line at the top where we want the header to display:

<%= render :partial => 'header' %>

Now, in header.rhtml, we can we can whip out some Ruby logic to deal with the problem of placing the "selected" class on the correct link, like so:

<a href="blog.html"
<%= "class='selected'" if controller.controller_name == 'blog' %>
>blog</a>

This is pretty intuitive. We're telling Rails to add the "selected" class only if the name of the current controller is 'blog'. We can get even more specific by adding the action_name into the mix:

<a href="bio.html"
<%= "class='selected'" if controller.controller_name == 'info'
&& controller.controller_action == 'bio' %>
>bio</a>

We could go even a step further and specify the link in proper Rails format (in the above examples, the links would have to be absolute):


<% if controller.controller_name == 'info'
&& controller.controller_action == 'bio' %>
<%= link_to 'bio', :controller => 'info',
:action => 'bio',
:class => 'selected' %>
<% else %>
<%= link_to 'bio', :controller => 'info',
:action => 'bio' %>
<% end %>

Now, this seems a little more complicated than the plain HTMl version, but a dense, contained chunk of complexity is more easily maintained than a sprawling sea of simplicity.

And, once we make the jump to start using partials, we can take advantage of another powerful feature: the ability to dynamically pass objects into them from the parent views. Normal views have available to them exactly the data made accessible by their controllers. But partials can be called from multiple different controllers. Hence, they get their data by having it passed into them by the view that calls them.

Let's look at an example. One of the things that I'm going to want to do on the At Dusk website is display At Dusk songs. I'm going to want to do this repeatedly and from many different places, so it probably makes sense to create a partial for it in a publicly accessible place. How about a folder in the views directory called 'shared'? Now what are we going to call our partial? Well, there will probably turn out to be lots of different formats in which we'd want to display tracks: as part of a list, in detail including lyrics, etc. We'll start with the the simplest one, the list display. So we'll call our partial _track_in_list.rhtml (remember the underscore is how Rails knows it's a partial) since it's going to display a single track as part of a list.

So, say we're on a page about a particular album and we want to display a list of the tracks on that album, how would we do it? (All through here I'm going to assume that there's a database and Rails models and controllers, and all the workings set up in the standard way.)

<p>Here are the tracks on our most recent album:
<ul>
<%= render :partial => 'shared/track_in_list',
:collection => @album.tracks %>
</ul>

First off, note how we've given the relational location of the partial 'shared/track_in_list' and haven't used the underscore when referring to the file. Second, we've added something new, the collection. The collection is just the group of objects for which we want to display our partial. In this case, we're going to display it once for each track belonging to our album.

Now it's time to write the track_in_list partial itself. Here comes the only tricky bit. When you render a partial from a parent view it defines a local variable in the child AND NAMES IT AFTER THE CHILD'S FILE NAME. I put that last part in all caps because it is both the thing it took me the longest to internalize and the key to the whole business. In our example, this means that, in the track_in_list partial, we'll have a local variable available to us called track_in_list that stores the current track being rendered.

So for example in _track_in_list.rhtml, we could say:

<li><%= track_in_list.title %></li>

and it would act as expected. But there's two problems with this. First, I find this code confusing. What's a track_in_list? If I don't immediately know off the top of my head all of the places from which this partial is being called, I might have a hard time remembering what actual object it is that we're rendering. Also, this code is useless anywhere outside this file. If I wanted to move this list element into another partial, say _track_detail.rhml, I'd have to change the variable name to track_detail. That doesn't sound too bad when you're talking about moving one line, but it really doesn't scale.

Therefore, I'll add one more line to the top of this partial:

<% track = track_in_list %>
<li><%= track.title %></li>

All I'm doing is storing the object that Rails automatically passed in (in this case, it was @album.track) into a new local variable that has a sensible name, "track". Once I've done that, I can just refer to track throughout the rest of the partial to access the current object. This will prevent a lot of pain if I decide that I want to rename the partial. Say that we wanted to be able to display movies as well in this list format, so change the file name of the partial to _media_in_list.rhtml. All I have to do is change this one line at the top and the rest of my view logic will still work.

So that was a (none-too-brief it turns out) introduction to partials in Rails. If nothing else, hopefully this tutorial will at least make it easier for you to find your way through other people's Rails apps by following the locations of nested partials. As I said above, the key thing to remember is that in each partial there's a local variable named after the partial's file name that holds whatever was passed in. If you can't think of anything to use partials for go read someone else's more complicated code. You'll soon see that the more comfortable you get with this little bit of complexity, the soon you can make your whole life in Rails that much simpler.

Tagged: , , , , , , ,