S.E.A.N.I.C.U.S.

Monday, April 24, 2006

Richer Helpers

I learned a little about how ERb works today. What I've been trying to do is create a helper method that allows me to output a complex layout element (multiple div) and pass it an ERb block in my HTML template, but have the helper wrap its code around the block, much like the form_for method. I planned to use the XML Builder since it provides nice semantics and visually beautiful code. I thought at first that it would be as simple as using yield in the middle of my helper. I struggled with this for quite a while, but I finally discovered that when Rails encounters the block, it's going to output it to the browser automatically, even if that block is passed to the method. The solution was to use a capture, then steal a little trick from form_for: the concat(text, context) method. Here's some sample code:

# In your helper class
def complex_layout(..., &block) # &block last in params
raise ArgumentError, "Missing block" unless block_given?
buffer = capture(&block)
xm = Builder::XmlMarkup.new
xm.div {
...
# the point of your layout
# where you want the passed block to appear
xm << buffer
...
}
concat(xm.target!, block.binding)
end

What does this do exactly?
  1. The raise statement requires that a block is passed.

  2. The capture statement stores the ERb output of the block into buffer.

  3. The next set of statements create the Builder and construct the markup.

  4. The xm << buffer statement pushes the contents of buffer into your Builder markup without escaping the contents

  5. Finally, concat uses the block's binding (calling context) to output the contents of the Builder markup.


I love how Rails has allowed me to gradually discover more about the Ruby language and become more adept at using its advanced features. While I don't completely understand Procs and blocks and all of that, I'm much closer now.