Rendering Collections in Rails

Joël Quenneville

Partials are a great way to break down complex view into more manageable chunks as well as keeping view code DRY. However, rendering a partial for each item in a collection looks ugly:

<% @user.comments.each do |comment| %>
  <%= render partial: 'comments/comment', locals: { comment: comment } %>
<% end %>

Partial paths

Luckily, Rails gives us this beautiful shorthand syntax:

render @user.comments

How does this magic work? Under the hood, render calls to_partial_path on each of our objects to determine which partial to render. Models that inherit from ActiveRecord::Base will return a partial name based on the model’s name by default. For example:

User.new.to_partial_path
# => 'users/user'

You can override this:

class User < ActiveRecord::Base
  def to_partial_path
    'users/profile'
  end
end

POROs

This works with plain old Ruby objects too. Here’s a Null Object Pattern example:

class Guest
  def name
    'Guest'
  end

  def to_partial_path
    'users/user'
  end
end

@dashboard.users_online is a mix of ActiveRecord User objects and non-persisted Guest objects:

  <h1>Users online:</h1>
  <%= render @dashboard.users_online %>

The same partial is used for both guests and registered users:

  <%= user.name %>

Heterogeneous collections

It gets even better. The objects in the collection can be of different types, each with their own partial.

@user.favorites can contain any combination of Article, Comment, and Image objects:

<h1><%= @user.name %>'s Favorites</h1>
<%= render @user.favorites %>

app/views/articles/_article.html.erb:

<h1><%= article.name %></h1>
<p><%= article.content %></p>

app/views/comments/_comment.html.erb:

  <div class="comment">
   <em>Last updated: <%= comment.updated_at %></em>
   <p><%= comment.content %></p>
  </div>

app/views/images/_image.html.rb:

  <h1><%= image.title %></h1>
  <%= image_tag image.url %>

The beauty of this polymorphic approach is that we don’t have to write any conditional code. As long as all the objects in the collection define to_partial_path, it all just works.

What’s next

If you found this useful, you might also enjoy: