Pretty Blocks in Rails Views

One of the easiest ways to improve the readability and reusability of a Rails application is to refactor the view layer. I find that most Rails code I look at in models and controllers tends to be very good, but views are a huge mess of single-use partials, repeated behavior, and lots and lots of missed opportunities to implement some really graceful and reusable helpers. I wanted to whomp up a quick blog post discussing some of my favorite techniques for making views prettier.

Use Blocks in Helpers

Ruby makes extensive use of blocks. So as good Rails programmers we want to follow in that grand tradition and make everything that can be blocky blocked. One of the first pieces of code I add to the application helper for a project is an override to link_to to accept a block:

def link_to(*args,&block)
  if block_given?
    concat(super(capture(&block), *args), block.binding)
  else
    super(*args)
  end
end

This functionality is already in Edge Rails, and if you actually use this helper there it will fail because of it. This makes it extremely easy to make links of all kinds in your application:

<% link_to(users_path) do %>
  <%= image_tag "user_button.png" %>
  Click here to add a user
<% end %>

Imagine how ugly that would be without the use of a block! It would be one big multi-line mess. Here, we have clarity and ease of use, and we don't sacrifice anything. In this example I use the two most important methods for making views accept blocks: concat and capture. Through a more common use case, let's take a closer look at these methods, and why block helpers can make your life a whole lot easier.

Rounded Corners

We want to add some rounded corners to our application. Without any code, this is how we make them:

 
<div class="rounded_box">
<div class="rounded_corner_content">Rounded corners are so Web 2.0.</div>
</div>
 

Initially it might be tempting to make two methods to output the start and end of the helper:

<%= rounded_top %>
Rounded corners are so Web 2.0.
<%= rounded_bottom %>

These are easy helper methods that we can clearly understand. Here's what the first looks like.

def rounded_top
  return '<div class="rounded_box">\n' +
    '<div class="rounded_corner_top"></div>\n' +
    '<div class="rounded_corner_content">'
end

But now we have a fair amount of ugly HTML in our helper. And will there ever be a case that we call rounded_top without wanting rounded_bottom an arbitrary amount of time afterwards? The answer would be no, so let's link the two together in a much more clever manner.

<% rounded_block do %>
Rounded corners are so Web 2.0.
<% end %>

This is an ERb evaluation block, not an output block: this is because we are going to replace the content directly on the page rather than output anything at all. (Thus our helper method will function similarly to other Rails evaluation blocks like form_for.) So let's make it happen.

def rounded_block(&amp;block)
  concat(content_tag(:div, :class =&gt; "rounded_box") do
    content_tag(:div, " ", :class =&gt; "rounded_corner_top") +
    content_tag(:div, :class =&gt; "rounded_corner_content") do
      capture(&amp;block)
    end +
    content_tag(:div, " ", :class =&gt; "rounded_corner_bottom")
  end, block.binding)
end

Let's dissect this helper quickly. Rounded_block accepts one argument, a block: in the middle, we capture that block, removing it from the page and returning it inside the helper. (We call this instead of yield, because if we yielded the block, it would appear twice on the page.) We add three content divs together and finally concat the whole shebang onto the block's binding which causes it to actually be displayed in the layout. Remember the plusses! Without them you'll only get the last content_tag, which is almost certainly not what you want.

This method is highly extensible and extremely flexible. Here's an example of the rounded_corner code I actually use in my applications:

<% rounded_yellow_box(:background => true, :title => "What I think about rounded corners", :id => "rounded_thoughts" ) %>
  They're so Web 2.0-y.
<% end %>

And the code for it:

def rounded_box(color, options = {}, &amp;block)
  raise ArgumentError, "Missing block" unless block_given?
  options.symbolize_keys!
    concat(content_tag(:div, :id =&gt; options[:id], :class =&gt; "rounded_box") do
      if options[:title] then content_tag(:div, options[:title], :class =&gt; "rounded_title") else "" end +
      content_tag(:div, " ", :class =&gt; "rounded_corner_top") +
      content_tag(:div, :class =&gt; if options[:background] "rounded_corner_content" else "rounded_corner_no_bg" end) do
        capture(&amp;block)
      end +
      content_tag(:div, " ", :class =&gt; "rounded_corner_bottom")
    end, block.binding)
end
 
def method_missing(method, *args, &amp;block)
  if method.to_s =~ /^rounded_([a-z]+)_box$/
    rounded_box($1, *args, &amp;block)
  else
    super
  end
end

A little dense, but extremely flexible: you can use helper methods like this to generate a lot of content situationally and change your block elements around quite a bit. Having this sort of agility can be invaluable in projects that demand many similar but different parts of the view layer.

Extending Blocks to Partials

Let's be honest: that rounded_block helper, though attractively simple, uses a fair amount of content_tags. Can we make this even simpler? Turns out we can by employing a little-used feature of partials: layout.

show.html.erb
<% render(:layout => 'rounded') do %>
  Rounded corners are so Web 2.0.
<% end %>

_rounded.html.erb:
<div class="rounded_box">
  <div class="rounded_corner_top"> </div>
    <div class="rounded_corner_content"><%= yield %></div>
  <div class="rounded_corner_bottom"> </div>
</div>

Or to really simplify your life, you can do the partial render on one line, if you're trying to put one partial into our rounded_corner layout:

_content.html.erb
Rounded corners are so Web 2.0.

show.html.erb
<%= render :partial => "content", :layout => "rounded" %>

Couldn't be easier. You can even pass locals and collections to the partial call just as you might to a regular call to render partial:

<% render(:layout => 'rounded', :locals => {:title => "What I think about rounded corners", :id => "rounded_thoughts", :background => true}) do %>
  Rounded corners are so Web 2.0.
<% end %>

And then in _rounded.html.erb:

<div class="rounded_box">
  <div class="rounded_corner_top" id="<%= id %>"> </div>
    <% if title %>
      <div class="rounded_title"><%= title %></div>
    <% end %>
    <div class="<%= background ? "rounded_corner_content" : "rounded_corner_no_bg" %>"><%= yield %></div>
  <div class="rounded_corner_bottom"> </div>
</div>

To Sum It All Up

So which way is better? Both solutions offer a lot to DRY up your application with: they make your code extremely reusable and powerful. Theoretically speaking the method_missing happiness I inserted earlier is slow, but the pages will probably be cached at this point anyway so really it's just a matter of style. I tend to use helper methods when things seem extremely programmatic to me: passing an options hash is very Rails-y and sensible. Conversely, I use partials when the layout is almost entirely HTML that won't be changing very much. But really the two methods are very interchangeable: just make sure to use them when you've got a lot of repeating code!

And as a small end-note: Edge Rails is changing the way that concat and capture work. concat will no longer need a binding to be attached to the page. Now it binds directly to the new @erbout instance variable and is appended straight to the buffer, which not only improves performance, but increases the readability of the helpers I made above.

Related Services: Ruby on Rails Development, Custom Software Development

Related posts:

  1. Hide and Go Seek, and Stay DRY (Part 1. Views)
  2. RSpec and Rails Custom Form Builders
  3. Rails 2.2 For Me And For You
  4. HTML + Code Markup: Threat or Menace, Part Two
  5. Rails Development for iPhone with rails_iui

Topics: ,

Comments: 19 so far

  1. Nice article Josh. I love posts that change the way I think about doing things in rails.

    Comment by Mike Breen, Tuesday, July 1, 2008 @ 4:02 pm

  2. Well done, Josh. I think your evaluation of the common state of view code is spot on (I know it’s true for MY code) and the suggestions you put forth are good examples of how developers should be thinking when putting together the UI of their application.

    Comment by ethan vizitei, Tuesday, July 1, 2008 @ 8:01 pm

  3. Good post. I’ve been using these techniques for a while and it does make views code so much more readable and nicer to look at. :)

    Comment by Alistair Holt, Wednesday, July 2, 2008 @ 6:37 am

  4. Really nice article Josh, I didn’t know you could yield in partials.
    This is gonna clean up my code a lot.

    Comment by Roy van der Meij, Wednesday, July 2, 2008 @ 8:11 am

  5. Nice post. But you really should try out HAML and the ultimate cool concat helpers. Your helper methods will look much nicer and the output too. :)

    Comment by Benny, Wednesday, July 2, 2008 @ 12:38 pm

  6. [...] Pretty Blocks in Rails Views – Some ways to make your code more elegant. [...]

    Pingback by A Fresh Cup » Blog Archive » Double Shot #240, Thursday, July 3, 2008 @ 6:06 am

  7. Hi Josh – great article. Some useful tips in there.

    Comment by Ric, Thursday, July 3, 2008 @ 11:00 am

  8. Hi, How would you make a make a method yield a block which contains tags?

    eg.
    In Helper:
    def not_for_customer
    yield
    end

    In View:

    ‘our_ref’ is not rendered…

    Comment by Kris, Friday, July 4, 2008 @ 6:49 am

  9. Absolutely awesome post. I’m getting my whole team to refactor today using some of the concepts you’ve mentioned above. And we can really use the RoundedCorners example above!!

    Cheers mate!
    Aditya

    Comment by Aditya Sanghi, Thursday, July 10, 2008 @ 3:25 am

  10. Good points. Much of this was discussed before, but reminders are always good.

    http://www.igvita.com/2007/03/15/block-helpers-and-dry-views-in-rails/

    Comment by chris, Friday, July 11, 2008 @ 11:09 pm

  11. Well-written article and your code really pushes deep into rails helpers. Well done!

    Unfortunately, readers should be cautious of injecting HTML syntax directly into helper methods in ruby. Typically, the larger the project, the faster the HTML changes. And if your designers aren’t familiar with ruby, then change becomes hard.

    Comment by J. Ryan Sobol, Saturday, July 12, 2008 @ 8:02 pm

  12. Choosing a different template engine might improve readability and reusability even further. I use haml and jquery, and all I do is:

    .corner Rounded corners are so Web 2.0.

    and in application.js:

    $(’.corner’).corner();

    and in css e.g.:

    .corner { background:#ffe; border:1px solid #999; }

    Can’t get easier then this.

    Comment by Lawrence Pit, Sunday, July 13, 2008 @ 8:10 pm

  13. There are a couple of errors in the code samples in the “Here’s an example of the rounded_corner code I actually use in my applications” part of this post.

    First, the “rounded_yellow_box” call needs a “do” at the end of it.

    And “if options[:background]” needs to be followed by a “then”.

    Comment by Keith Carter, Tuesday, July 15, 2008 @ 8:47 pm

  14. Thanks for the advice, really helpfull. I prefer use the rails default (prototype and scriptaculous )and how good will be if prototype add corner method to your features.

    Comment by Daniel Lopes, Thursday, July 31, 2008 @ 12:44 pm

  15. Very interesting!

    These techniques opens up many possibilities, some even very strange:

    in view:

    a_method_to_be_called(with_params)

    the ajax_call in the helper:
    concat(
    link_to_remote(”Add”, :url=> {:controller => …, :action => …, :block => capture(&block)}
    ), block.binding)

    This means we can pass to controllers functions to be executed, functions defined freely in views.

    Does this sound weird for you?

    Comment by cs, Friday, October 17, 2008 @ 7:04 am

  16. sorry, the view code was sanitized …

    in view:
    ajax_call(params) do
    a_method_to_be_called(with_params)
    end

    Comment by cs, Friday, October 17, 2008 @ 7:06 am

  17. [...] This may be the most subtle change I’ve ever been this excited about. Let’s go back to the bad old days — last week — and say that you wanted to write a block helper method like this: [...]

    Pingback by Pathfinder Development » Rails 2.2 For Me And For You, Friday, October 31, 2008 @ 2:25 pm

  18. [...] Pretty Blocks In Rails Views [...]

    Pingback by Ruby on Rails » 2008: A Year That Was » Pathfinder Development, Friday, December 26, 2008 @ 11:54 am

  19. You’re my savior Josh. I had been messing around with helpers methods, and didn’t like the result.

    Your technique is much more elegant and certainly more readable and updatable.

    You rock !

    Comment by jlfenaux, Monday, February 16, 2009 @ 4:28 am

Sorry, the comment form is closed at this time.

Launch: Pathfinder Newsletter

    Get a monthly update on best practices for delivering successful software.

    Subscribe via email


    Subscribe via RSS      RSS icon

Topics

Search

WordPress

Comments about this site: info@pathf.com