{ |one, step, back| }

Builder Objects
24 Aug 04 - http://onestepback.org/index.cgi/Tech/Ruby/BuilderObjects.rdoc

Builder Objects

Sometimes you get good ideas from other languages.
I’ve had fun with this little package over the past few days. The Groovy language was the big buzz at the No Fluff, Just Stuff conference (see NoFluff2004) a few weeks ago. So I’ve been looking at some groovy stuff recently (and I did a Groovy presentation at our local Java group), and I stumbled across the builder library that comes with Groovy. "What a cool idea" I thought and saw that it would be easy to implement in Ruby. So here is the result.

The basic idea is that you create a builder object and then send it messages. It responds to the messages by building up a data structure based on those messages.

Ack, it’s easy to see it in action than to describe it. For example, the following code …

  builder = Builder::XmlMarkup.new("", 2)
  puts builder.person {
    name("jim")
    phone("555-1234", "local"=>"yes")
    address("Cincinnati")
  }

will print …

  <person>
    <name>jim</name>
    <phone local="yes">555-1234</phone>
    <address>Cincinnati</address>
  </person>

So, an XML Markup object produces XML markup. Not very surprising until you think about the methods send to the builder. Where is the person method defined? Or the name method?

Actually, they are not defined anywhere. Instead we have defined a generic method_missing method and handles any undefined method and adds the name of the method to the XML markup we are building. In addition we use code blocks to capture the nested nature of the XML. The result is a very natural way to programmatically generate XML markup.

And it goes beyond just markup. If you use Bulder::XmlEvents instead of Builder::XmlMarkup, the same syntax will generate SAX-like events instead of markup. Or you can use a DOM builder to generate a DOM object tree (I haven’t implemented the DOM version, but it should be straightforward).

There are a couple of "gotchas" with this approach that you should be aware of …

Because Builder objects lack the normal methods that most objects have makes working with them rather interesting. Consider the following irb session where we create a builder object and let irb try to display it (by sending it an inspect message):

    >> x = Builder::XmlMarkup.new
    => <inspect/>
    >> x
    => <inspect/><inspect/>
    >> x.to_s
    => "<inspect/><inspect/><to_s/>"
    >> x.methods
    => "<inspect/><inspect/><to_s/><methods/>"
    >> x + 5
    => "<inspect/><inspect/><to_s/><methods/><+>5</+>"
    >> x
    => <inspect/><inspect/><to_s/><methods/><+>5</+><inspect/>

Every time we send a message to the builder it records the name of the message. In a way, the builder has become a trace object, recording all the messages send to it. Weird.

Finally, consider this last example. Suppose we have a method that uses a builder to generate an RSS feed. Here is the method in part ..

  def generate_rss(builder, a_title, a_link, items)
    builder.rss("version" => "0.91") {
      channel {
        title a_title
        link  a_link
        # ... code missing ...
        items.each do |it|
          item {
           title it.title
           link  it.link
           description it.description
          }
        end
      }
    }
  end

Now a single method (generate_rss) can generate XML markup text, a DOM tree or a series of SAX events, all depending on what kind of builder object you pass to the function. That’s slightly cool.

As I mentioned, I got this idea from the Groovy language. Groovy has some other builders as well. For example there is an Ant task builder that integrates with ant. It is a very flexible technique that really shows off the power of a dynamic language.

Note: You can find the Builder object code in the RubyGem builder-0.1.1.gem available on the RubyGem server on RubyForge.