Partials: Abuse of them, don’t abuse them

TL;DR; Use partials, they are key to reducing duplication (DRY) – and to keeping your front end maintainable. Apply the same logic to partials as you do to your back-end code – templates are code.

Introduction

A puzzle undone, which forms a cube.

Building blocks. (Photo credit: Wikipedia)

I’ve been playing a lot with partials these days, and I want to take this opportunity to share some practices, advices and findings about them, and some tips on how we to apply most of our development best practices to them.

To be as practical as possible, let’s create a Blog application (this will not win me any originality prize, but should be comfortable for everyone to follow, which is the point).

Sample

Let’s say we are working on the index page showing the various Posts on our Blog. We have a nice loop on the @posts array to show all our Posts. Note that we use each_with_index in order to be able to generate different ids for each <article> element.

<% @posts.each_with_index do |post, index| %>
 <article id=”<%= index %>” data-category="<%= post.category %>">
   <header>
     <h1><%= post.title %></h1>
     <p>By <%= post.author %></p>
   </header>
   <section class="contents">
     <%= post.contents %>
   </section>
   <footer>
     Published on <%= post.publish_date %>
   </footer>
 </article>
<% end %>

Before that, we may have a header with general informations or statistics:

 <section id="statistics">
   <p>Currently <%= @posts.size %> in <%= @posts.map(&:category).uniq.size %> different categories.</p>
   <p>Most recent category: <%= @posts.first.category %>
 </section>

And also a button to filter posts along their category:

<section id="filters">
   <div class="btn-group sort">
     <a class="btn dropdown-toogle" data-toogle="dropdown" href="#">All categories <span class="caret"></span></a>
     <ul class="dropdown-menu">
       <li><a data-filter-category="all_filter" href="#">All categories</a></li>
       <li><a data-filter-category="opinion" href="#">Opinion</a></li>
       <li><a data-filter-category="announcement" href="#">Announcement</a></li>
       <li><a data-filter-category="debate" href="#">Debate</a></li>
     </ul>
   </div>
 </section>

Finally, thanks to Rails, we use a general layout file:

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="utf-8"/>
 <title>Blog</title>

 <%= stylesheet_link_tag "application" %>
 <%= javascript_include_tag "application" %>
 <%= csrf_meta_tags %>

 <%= yield :head %>

</head>
<body>

<div class="container">

<%= yield %>

</div>

</body>
</html>

Notice the “yield :head” allowing our index.erb.html page to add something to the <head> part, in this case the JavaScript needed to make the filter work:

<% content_for :header do %>
 <%= javascript_include_tag "filters" %>
<% end %>

Look here and here for the complete gists. This situation is rather simple, but let’s use partials to make it even simpler.

0. Use partials

First, we extract the code needed to show a post in its own partial, _post.html.erb. Partials start with an underscore to differentiate them from regular views. Be aware that – when referencing them – you never specify the underscore.

_post.html.erb

<article id=”<%= index %>” data-category="<%= post.category %>">
   <header>
     <h1><%= post.title %></h1>
     <p>By <%= post.author %></p>
   </header>
   <section class="contents">
     <%= post.contents %>
   </section>
   <footer>
     Published on <%= post.publish_date %>
   </footer>
</article>

In order to show it in index.html.erb, we need to add a call to render:

<% @posts.each_with_index do |post| %>
 <%= render partial: "post", locals: {post: post} %>
<% end %>

The render call specifies the partial to user (here _post, remember that you do not use the underscore when calling render), and any variables that the partial needs.

1. Simplify partial calls

These render calls can actually be simplified in different ways. First, if you only need one object with the same name as the partial, you can use the object: parameter:

<% @posts.each_with_index do |post| %>
 <%= render partial: "post", object: post %>
<% end %>

Second, if the parameter class actually has the same name as the partial, rails can deduce the partial name from the object class (not the variable name, the class), allowing calls like:

<% @posts.each do |post| %>
 <%= render post %>
<% end %>

Hard to be simpler than that.

2. Use partials for collections

Well – in this case – it is. As we are using the partial to render a collection (we call the partial for each post in @posts), we can use Rails’ handy collection argument, removing the need for the loop:

<%= render partial: "post", collection: @posts %>

Of course, the magic based on the class works again, so this is doing exactly the same:

<%= render @posts %>

Ae did forget something: the index for setting our id. We could back the each_with_index, but we would have to make the loop explicit again. There is a better solution: when rendering a collection, rails creates a special variable using the name of the object (post in this case) postfixed with _counter, that holds the index of the loop, allowing us to do something like:

<article id=”<%= post_counter %>”>

3. Use Locals instead of global variables “@” in partials

Your partials should never use your instance variables (ie, variable with @) – this is important for decoupling: you want to treat the partial like a method, using only its given parameters. As an example, let’s extract a partial for the statistics part:

index.html.erb

<%= render partial: "statistics" %>

_statistics.html.erb

<section id="statistics">
   <p>Currently <%= @posts.size %> in <%= @posts.map(&:category).uniq.size %> different categories.</p>
   <p>Most recent category: <%= @posts.first.category %>
</section>

It works, but using the @posts is bad practice – similar to a global variable, it would make the partial difficult to reuse in any other context (the variable could have another name like @popular_posts). We can simply use posts, and send the collection as an argument to the render call:

index.html.erb

<%= render partial: "statistics", locals: {posts: @posts} %>

_statistics.html.erb

<section id="statistics">
   <p>Currently <%= posts.size %> in <%= posts.map(&:category).uniq.size %> different categories.</p>
   <p>Most recent category: <%= posts.first.category %>
</section>

4. Using content_for instead of to facilitate reuse

Finally, let’s create a partial for the filter part. This one does not use any variable, so it is quite easy:

index.html.erb

<%= render partial: "filters" %>

_filters.html.erb

<section id="filters">
   <div class="btn-group sort">
     <a class="btn dropdown-toogle" data-toogle="dropdown" href="#">All categories <span class="caret"></span></a>
     <ul class="dropdown-menu">
       <li><a data-filter-category="all_filter" href="#">All categories</a></li>
       <li><a data-filter-category="opinion" href="#">Opinion</a></li>
       <li><a data-filter-category="announcement" href="#">Announcement</a></li>
       <li><a data-filter-category="debate" href="#">Debate</a></li>
     </ul>
   </div>
 </section>

Now, the _filters partials depends on its JavaScript import, which is still in the index page. content_for allows us to add reference to the header from any place, including partial. Let’s move it there:

<% content_for :header do %>
 <%= javascript_include_tag "filters" %>
<% end %>
<section id="filters">
   <div class="btn-group sort">
     <a class="btn dropdown-toogle" data-toogle="dropdown" href="#">All categories <span class="caret"></span></a>
     <ul class="dropdown-menu">
       <li><a data-filter-category="all_filter" href="#">All categories</a></li>
       <li><a data-filter-category="opinion" href="#">Opinion</a></li>
       <li><a data-filter-category="announcement" href="#">Announcement</a></li>
       <li><a data-filter-category="debate" href="#">Debate</a></li>
     </ul>
   </div>
 </section>

This does not change anything in the result, but it bundles everything the partial needs with it. Should we want to use our super filter partial on another page, it has everything it needs to work, avoiding to create constraints on the page that imports it.

Conclusion

I really like partials because they allow me to apply my coding design principles to the front end, as shown in this post:

Encapsulation

  • “Clarify your dependencies on the external world” by using proper locals
  • “Do not assume or mandate the external word” : by appropriate usage of content_for

Good responsibilities

  • A partial is not only a technical trick to avoid too long pages – it is really a component that
    Puzzle cube; a type of puzzle.

    My application. (Photo credit: Wikipedia)

    should represent something in the application.

  • A partial can be seen as the visual representation of one of your objects. If you have a good detailed model, partials should follow roughly the same structure, guaranteeing consistency on the way an object is represented in the different parts of the application.

Enhanced by Zemanta




6 thoughts on “Partials: Abuse of them, don’t abuse them

  1. Hugues

    Thanks for the info.

    Is it me or did I notice a typo: In section “1. Simplify partial calls”, the code is the same that the code just above ? Ie, the “object:” parameter is not used.

    Reply
  2. Dan Milliron

    Excellent, excellent article. Thanks you! I did not know about the _counter feature, and you helped steel my resolve to use locals instead of @-variables.

    I believe there is a typo in section “3. Use Locals instead of global variables “@” in partials”. It should read, “locals: {posts: @posts}”, instead of “locals: {post: @posts}”

    Thank you for writing this!

    Reply
    1. Martin Post author

      Dan,
      Thanks for the kind words, and well spotted for the variable name, I fixed that just now.

      Keep the resolve, @-variables in partials are a practice we need to get rid of!

      Martin

      Reply
  3. Jeff

    Good review and learned something new (i.e. _counter)!

    I believe that

    should be

    <%= yield :header %>

    for your example to work.

    Reply
    1. Martin Post author

      Hi Jeff,
      Yes, I was pretty glad to find the _counter thing too – the small things that help to make the code better.
      Well spotted for the typo (again, three in three comments!).

      Thanks,

      Martin

      Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>