Blocks rock and I couldn’t agree more. The functional programming aspect of ruby has started to interest me more and more. Blocks are used all over the place, with respond_to, collect, returning and ActionView::Helpers::FormHelper#form_for.

Sometimes, a design requires a little extra markup, perhaps something to get that rounded corner to work or what-have you. Now let’s say this piece of code requires certain classes, a certain kind of structure, including a tile, and has to be used in many places, something like a sidebar piece. Instead of copying and pasting this structure over and over, you can use a helper to make you life easier, and if you combine it with a block, it will just feel even more painless.

The Setup

<div class="sidebar">
  <div class="bottom">
    <h3>Title</h3>
    <div class="body">
      <p>Some Content</p>
    </div>
  </div>
</div>

There are two elements which must change; the title and the contents of the body. The title should simply be just some text that goes in between the h3 tag. The contents of the body could be anything, in this case, it is the paragraph tag and everything inside.

Creating a Helper without a Block

Since we have these two varying things, we could create a helper method to encapsulate our structure. The first parameter of the helper will be the title while the second parameter will be the contents of the body.

def sidebar(title, body)
  content_tag(
    'div',
    content_tag(
      'div',
      content_tag('h3', title) + content_tag('div', body, :class => 'body'),
      :class => 'bottom'
    ),
    :class => 'sidebar'
  )
end

That looks pretty easy. Now let’s look at the interface for this helper

<%= sidebar('Hello World',
  '<p>It is one of these "Hello World" things again.</p>') %>

Not too shabby, but what if we wanted to do an unordered list?

<%= sidebar(
  'Hello World',
  '<ul>' +
    '<li>Item</li>' +
    '<li>Item</li>' +
    '<li>Item</li>' +
  '</ul>'
) %>

This feels pretty clunky and ERB is completely ignored. Instead of this procedural way of thinking, why not see what a block can do for us?

Creating a Helper +with+ a Block

For this to work, we will still need the first parameter to be the title, but the body will be passed in as a block to be processed from within our helper. The block we create will be ERB code, this will feel more natural to the person who has to implement the code. The magic to get this done comes from the concat method of ActionView::Helpers::TextHelper module.

def sidebar(title, &block)
  raise ArgumentError, "Missing block" unless block_given?

  start = <<-END
    <div class="sidebar">
      <div class="bottom">
        <h3>#{title}</h3>
        <div class="body">
  END
  concat(start, block.binding)

  block.call

  end = <<-END
        </div>
      </div>
    </div>
  END
  concat(end, block.binding)

end

This may look slightly odd, but I assure you, the interface makes up for it.

<% sidebar('Hello World') do %>
  <p>And now back to the way I was doing things before</p>
  <ul>
    <li>Item</li>
    <li>Item</li>
    <li>Item</li>
  </ul>
<% end %>

The Benefits

Published in Ruby on Rails

10 Responses to “Blocks and Helpers, a Lovely Combination”

  1. January 23rd, 2007 at 8:27 am #Steve

    This is really nice. Thanks for the tutorial. The only thing that it would be nice to clean up is the html inside the helper. Having to concatenate strings of html together is kind of tough to read.

  2. January 23rd, 2007 at 9:28 am #Logan Capaldo

    Pretty sure you mean “interface” when you say “implementation”. (The implementation would be the code in def sidebar …)

  3. January 23rd, 2007 at 9:47 am #Branden Timm

    How about something like this, to really embrace the functionality :) ? Where param html is an array of first-level div lines…

    def create_sidebar(html, &block)
      raise ArgumentError, “Missing block” unless block_given?
      case html.length
      when 1
        concat(html[0], block.binding)
        block.call
        concat(‘</div>’, block.binding)
      else
        concat(html[0], block.binding)
        create_sidebar(html[1..html.length], block)
        concat(‘</div>’, block.binding)
      end
    end

    My ruby is a little rusty so this isnt the most elegant implementation im sure…but the point remains that it feels natural to write nested div elements using a recursive algorithm.

  4. January 23rd, 2007 at 11:18 am #Jules

    Rails already supports this: @content_for :sidebar do … end@

  5. January 23rd, 2007 at 11:46 am #John

    I’m having trouble understanding:
    concat(start, block.binding)

    Wouldn’t this append start after the block’s text? If it doesn’t, then how is it possible that this works?
    concat(end, block.binding)

    wouldn’t the end go at the front of it all? sorry, somewhat confused by concat’s and bindings.

  6. January 23rd, 2007 at 12:43 pm #Adam

    *Branden*: That would be a good idea to create a generic implementation, more or less. With a little tweaking that could be awesome.

    *Jules*: there is that, but it’s not something that can be cached as a fragment.

    *John*: Look at the “@concat@ method”:http://api.rubyonrails.org/classes/ActionView/Helpers/TextHelper.html#M000611. It’s more of a function of Binding than it is a String’s concat. I got this syntax from rails’ “@form_for@ method”:http://api.rubyonrails.com/classes/ActionView/Helpers/FormHelper.html#M000491 and I’m not 100% sure how Bindings work as of yet.

  7. January 23rd, 2007 at 10:05 am #Adam

    *Steve*: Good point. I will fix that up so I’m not doing so many @concat@ calls

    *Logan*: You’re right. I had my terms mixed up. Thanks for the heads up.

  8. January 23rd, 2007 at 5:51 pm #Ron Green

    Thanks for this post! I was just looking for a way to encapsulate the opening and closing div elements needed for a rounded box design. This keeps those elements coupled whle yielding for the div contents! Thanks!

  9. January 24th, 2007 at 3:09 pm #Ronie Uliana

    You can use these 4 lines in your ApplicationHelper:

    def with_partial(name, options = nil, &block)
      content_for(name.to_sym, &block)
      concat(render(:partial => name.to_s, :locals => options), block.binding)
    end
    

    Then your partial could be:

      < % with_partial :sidebar, :title => 'Hello word' do %>
    
    And now back to the way I was doing things before
    
    • Item
    • Item
    • Item
    < % end %>

    And the partial _sidebar.rhtml could be:

    
    
  10. January 25th, 2007 at 12:04 pm #Adam

    That’s an interesting solution as well; however, it has an unwanted side-effect: If you use the same @with_partial :sidebar@ in more than one place on your page, you end up with duplicate information.

    The first time you call it, it works great. The second time you call it, it prints out the data from the first block AND the second block. The third time it renders the block information from the first call AND the second call AND the third; and so on and so on.

Leave a Reply