Blocks and Helpers, a Lovely Combination
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
1<div class="sidebar">
2 <div class="bottom">
3 <h3>Title</h3>
4 <div class="body">
5 <p>Some Content</p>
6 </div>
7 </div>
8</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.
1def sidebar(title, body)
2 content_tag(
3 'div',
4 content_tag(
5 'div',
6 content_tag('h3', title) + content_tag('div', body, :class => 'body'),
7 :class => 'bottom'
8 ),
9 :class => 'sidebar'
10 )
11end
That looks pretty easy. Now let's look at the interface for this helper
1<%= sidebar('Hello World',
2 '<p>It is one of these "Hello World" things again.</p>') %>
Not too shabby, but what if we wanted to do an unordered list?
1<%= sidebar(
2 'Hello World',
3 '<ul>' +
4 '<li>Item</li>' +
5 '<li>Item</li>' +
6 '<li>Item</li>' +
7 '</ul>'
8) %>
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.
1def sidebar(title, &block)
2 raise ArgumentError, "Missing block" unless block_given?
3
4 start = <<-END
5 <div class="sidebar">
6 <div class="bottom">
7 <h3>#{title}</h3>
8 <div class="body">
9 END
10 concat(start, block.binding)
11
12 block.call
13
14 end = <<-END
15 </div>
16 </div>
17 </div>
18 END
19 concat(end, block.binding)
20
21end
This may look slightly odd, but I assure you, the interface makes up for it.
1<% sidebar('Hello World') do %>
2 <p>And now back to the way I was doing things before</p>
3 <ul>
4 <li>Item</li>
5 <li>Item</li>
6 <li>Item</li>
7 </ul>
8<% end %>
The Benefits
- The implementation code is more readable
- It gives you one place to make your common edits
- It keeps your code DRY(don't repeat yourself)
Comments
comments powered by Disqus