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:

About thoughtbot

We've been helping engineering teams deliver exceptional products for over 20 years. Our designers, developers, and product managers work closely with teams to solve your toughest software challenges through collaborative design and development. Learn more about us.