Blocks and Helpers, a Lovely Combination

Monday, January 22, 2007

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