Block Helpers and DRY Views in Rails

Developers tend to spend a lot of time refactoring their controllers and helpers, but once they get to the views (rhtml), more often than not, the DRY (Don't Repeat Yourself) hat just disappears. This is not surprising, HTML markup by its very nature predisposes you to repetition of structural tags. However, in certain situations, a little Ruby magic can go a long way. Namely, you should get into the habit of creating block helpers to DRY up your views. Let's take a look at the often (ab)used case of rounded corners.

Rounded corners, the Ruby way

The Web 2.0 crowd has produced more rounded corner techniques than there are Web 2.0 sites (hard to believe, I know), so without picking any favorites I chose Scott Shiller's method solely for the sake of an example. Feel free to substitute your own favorite technique, as you will see in a second, the HTML is easy to change, the important takeaway is the Ruby magic.

Assuming you have imported the CSS stylesheet, your erb template is now likely to contain:

<div class="dialog">
 <div class="content">
  <div class="t"></div>
    <!-- Your content goes here -->
  </div>
  <div class="b"><div></div>
 </div>
</div>

Of course, you will want more than one rounded box, hence you will start duplicating this code all over your templates. Admit it, you've done it! Let's fix that. First thing you will notice is that the wrapping elements (divs) never change, making them a prime target for extraction. First solution: extract the divs into a partial, define a body variable and render the partial from the view. It could work, but it gets messy. Instead, we can use Err's method to trick a Ruby block into rendering a partial for us, giving us a nice readable interface that even a non-Ruby developer will understand:

<!-- In your .rhtml template... -->

<% rounded_box 'Box Title!' do %>
    Look ma, a clean rounded box, minus the code duplication!
<% end %>

Making a Ruby block render a partial

The Ruby magic happens with the help of two helpers functions: block_to_partial, and rounded_box. Let's take a closer look at the code:

# Only need this helper once, it will provide an interface to convert a block into a partial.
  # 1. Capture is a Rails helper which will 'capture' the output of a block into a variable
  # 2. Merge the 'body' variable into our options hash
  # 3. Render the partial with the given options hash. Just like calling the partial directly.
def block_to_partial(partial_name, options = {}, &block)
  options.merge!(:body => capture(&block))
  concat(render(:partial => partial_name, :locals => options), block.binding)
end

# Create as many of these as you like, each should call a different partial
  # 1. Render 'shared/rounded_box' partial with the given options and block content
def rounded_box(title, options = {}, &block)
  block_to_partial('shared/rounded_box', options.merge(:title => title), &block)
end

# Sample helper #2
def un_rounded_box(title, options = {}, &block)
  block_to_partial('shared/un_rounded_box', options.merge(:title => title), &block)
end

Finally, we extract the HTML code into a separate partial:

<div class="dialog">
 <div class="content">
  <div class="t"></div>
  <h2><%= title %></h2>

     <%= body %>

 </div>
 <div class="b"><div></div></div>
</div>

It's an extremely elegant solution to an ugly problem - we wrapped a render call into a nice-looking Ruby block. Say goodbye to code duplication and revel in the newfound joy of your cleaned up Erb templates! Tip of the day: be zealous about it, you'll love yourself for it later.


Ilya Grigorik

Ilya Grigorik is a web performance engineer and developer advocate at Google, where his focus is on making the web fast and driving adoption of performance best practices at Google and beyond.