Partials

The Very Short Story

If, in a view, you call

  render_partial 'item', object

Rails will find the file _item.rhtml, evaluate it (using the object you passed), and insert the result into the view. This is a good way of refactoring view logic.

Purpose and Overview

Partials ("partial views") are fragments of RHTML that can be inserted into a view. They exist to make the view logic simpler. The most typical usage is rendering a list of items. The view logic for each item is encapulated in a partial, and the view simply iterates through them, perhaps like this:

  <% for item in @list %>
    <%= render_partial 'item', item %>      <!-- Note the equal sign! -->
    <%= link_to "Top", nil, :href => "#top" %>
    <hr />
  <% end %>

The first parameter to render_partial is the name of the partial; the second parameter is the object you want to "pass" to it.

Rendering a partial is similar to calling a method: it has a name and it has parameters. There are different ways of passing those parameters, which we deal with below.

If there are different ways to display an item (brief or verbose, say), you can use one partial for each type of display, e.g. item_brief and item_verbose. Then you can easily change the view code, or even select the appropriate partial at runtime. The partial is encapsulating a piece of view logic which can even be reused in several views.

Since rendering a list of partials is a common activity, there is a shortcut for it using render_partial_collection. Note that the above example couldn’t use it, because extra HTML is being inserted within the loop.

Where Partials Live

Consider the following line:

  <%= render_partial 'todo_item', item %>

Rails will

  • look for a file named _todo_item.rhtml; and
  • look for that file in the same directory as the view.

Notice the leading underscore in the filename of the partial.

If you want to render a partial from a different directory, you can:

  <%= render_partial 'user/user_summary', @user %>
                      -----

In this case, Rails will look for app/views/user/_user_summary.rhtml.

Passing Parameters

You usually want to pass some information into the partial, e.g. a todo item to be displayed. There are three ways to achieve this. In order of complexity, they are: instance variables; passing a single object; and assigning local variables. We will look at the same example — rendering a single todo item — using these three techniques.

The way we render the todo item will be extremely basic: just the description and due date, separated by a colon. For instance:

  <p>Mow the lawn: 2005-06-13</p>

1. Instance Variable

This technique is like using global variables in regular programming: simple and ugly.

The view looks like this:

  <% for @item in @items %>                     <!-- "@item" is an instance variable -->
    <%= render_partial 'item_brief' %>
  <% end %>

The partial looks like this:

  <p><%= @item.text %>: <%= @item.due_date %></p>

You can see that no parameter was passed in render_partial. There was no need to; instance variables that are "visible" in the view are also visible in the partial.

2. Single Object Passing

render_partial allows us to pass one object into the partial, and the partial accesses that object in a somewhat magic way: via a local variable named after the partial.

In this case, the view looks like this:

  <% for item in @items %>
    <%= render_partial 'item_brief', item %>    <!-- We pass in local variable "item" -->
  <% end %>

And the partial (_item_brief.rhtml):

  <p><%= item_brief.text %>: <%= item_brief.due_date %></p>

What’s going on here? The partial’s name is "item_brief", so within the partial, the local variable item_brief is assigned to the object passed in from the view.

Now, item_brief is a good name for our partial, but it’s a rotten name for the variable, so we can refactor it like this:

  <% item = item_brief %>
  <p><%= item.text %>: <%= item.due_date %></p>

Now we are naming the variable more appropriately and the partial reads better.

3. Assigning Local Variables

Finally, we can be explicit about local variables when we call render_partial. This is probably the clearest way to use partials.

The view:

  <% for item in @items %>
    <%= render_partial 'item_brief', nil, 'item' => item %>
  <% end %>

That assigns the local variable (within the partial) item to the object represented by the local variable (within the view) item. So the partial is simply:

  <p><%= item.text %>: <%= item.due_date %></p>

Notice the nil in the render_partial call above? That’s because we are not passing any object as per the "simple object passing" scheme described above.

Since our partial is effectively parametized now (it expects item to be set), we might like to put a comment in our partial to this effect.

  <!-- Parameter: item (a todo item) -->
  <p><%= item.text %>: <%= item.due_date %></p>

Summary

There are two advantages to the third method of passing parameters (assigning local variables):

  • It looks like other methods we’re familiar with in Rails (e.g. link_to "List items", :action => :list).
  • We can pass multiple variables. Say we wanted to assign different colors to different todo items.
      View:
        <%= render_partial 'item_brief', nil, 'item' => item, 'color' => 'red' %>
    
      Partial:
        <!-- Parameters: item (a todo item), color (a CSS color) -->
        <p style="color: <%= color %>"><%= item.text %>: <%= item.due_date %></p>
    

There are two things to look out for in using this method:

  • You need to remember to specify nil for the second argument to render_partial.
  • The local variable names need to be Strings (symbols would make more sense).

Despite those shortcomings, it has distinct advantages over the other two. The first method is like using global variables; the second method can be unclear, as it’s not intuitive that a variable would be named after the partial.

Rendering a Collection of Partials

Because a common use (probably the most common use) for partials is rendering individual items in a collection, there is a shortcut for rendering a collection of partials, called render_partial_collection. Thus,

  <% for item in @items %>
    <%= render_partial 'item_brief', item %>
  <% end %>

can be rewritten as

  <%= render_partial_collection 'item_brief', @items %>

When the partial is rendered, the local variables item_brief and item_brief_counter are defined. These (as discussed above) aren’t very good names, but you don’t have much control over parameter passing when using render_partial_collection.

The counter variable simply lets you know which element in the collection it is. That would be useful for alternating colors, for instance.

Because the local variable name is based on the name of the partial, you cannot apply the "assigning local variables" style of parameter passing when using render_partial_collection.

Inserting a Separator

One problem with rendering a collection of partials is there’s no easy way to insert a separator between them. Let’s imagine that separator is a horizontal line. How do you prevent that line from being rendered before the first item or after the last one? render_partial_collection has a solution for this.

  View:
    <%= render_partial_collection 'item_brief', @items, 'separator' %>

  _separator.html:
    <hr size="2"/>

  Output:
    <!-- HTML for item 1 -->
      <hr size="2"/>
    <!-- HTML for item 2 -->
      <hr size="2"/>
    <!-- HTML for item 3 -->
      <hr size="2"/>
             .
             .
             .
      <hr size="2"/>
    <!-- HTML for item N -->

The separator is just another partial, so the filename has the leading underscore and is located, typically, in the same directory as the view.

Conclusion

Partials are a great way to refactor your views, so that the main view isn’t cluttered with too many details. Also, it means that you can reuse fragments in several views in a natural way. Partials typically need to be parametized, and there are several ways of achieving that, all described above. Finally, we looked at a shortcut for rendering a collection of partials.

Remember:

  <%= render_partial 'item' %>                         or
  <%= render_partial 'item', item %>                   or
  <%= render_partial 'item', nil, 'item' => item %>

and

  <%= render_partial_collection 'item', @items %>

Also, if the partial lives outside the "current" view directory:

  <%= render_partial 'other_directory/item' %>

Be careful to include the equal sign; i.e. <%= render_partial not %< render_partial.

Methods
Public Instance methods
render_collection_of_partials(partial_name, collection, partial_spacer_template = nil, local_assigns = {})
render_partial(partial_path, object = nil, local_assigns = {})

Renders a partial at the current place in the view. See Partials for discussion and example usage.

Parameters:

  • partial_path is the name of the partial, e.g. "item", "user", "widget_verbose".
  • object (optional) is an object to be made available inside the partial, through a local variable named the same as the partial.
  • local_assigns (optional) is a hash of local variable assignments — ‘var’ => value, where var is a local variable inside the partial, and value is a value from the view. This is an alternative way of passing data into the partial.
# File partials.rb, line 239
    def render_partial(partial_path, object = nil, local_assigns = {})
      path, partial_name = partial_pieces(partial_path)
      object ||= controller.instance_variable_get("@#{partial_name}")
      counter_name  = partial_counter_name(partial_name)
      local_assigns = local_assigns.merge(counter_name => 1) unless local_assigns.has_key?(counter_name)
      render("#{path}/_#{partial_name}", { partial_name => object }.merge(local_assigns))
    end
render_partial_collection(partial_name, collection, partial_spacer_template = nil, local_assigns = {})

Renders a partial once for each item in the given collection. This is a shortcut for putting looping logic in the view. See Partials for discussion and example usage.

Parameters:

  • partial_name: as per Partials#render_partial.
  • collection is a collection (e.g. Array) of objects to be iterated.
  • partial_spacer_template (optional) is another partial to be rendered in between the items of the collection.
  • local_assigns: as per Partials#render_partial.

The following two code snippets are equivalent:

  <%= render_partial_collection 'item', @items %>

  <% @items.each_with_index do |item, index| %>
    <%= render_partial 'item', item, 'item_counter' => index %>
  <% end %>
This method is also aliased as render_collection_of_partials
# File partials.rb, line 263
    def render_partial_collection(partial_name, collection, partial_spacer_template = nil, local_assigns = {})
      collection_of_partials = Array.new
      counter_name = partial_counter_name(partial_name)
      collection.each_with_index do |element, counter|
        collection_of_partials.push(render_partial(partial_name, element, { counter_name => counter }.merge(local_assigns)))
      end

      return nil if collection_of_partials.empty?
      if partial_spacer_template
        spacer_path, spacer_name = partial_pieces(partial_spacer_template)
        collection_of_partials.join(render("#{spacer_path}/_#{spacer_name}"))
      else
        collection_of_partials
      end
    end