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 …
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 …
Within the builder code blocks, any method call with an implicit object
target needs to be sent to our builder. To achieve this, the code blocks
are evaluated with instance_eval which changes the value of
self to be the builder. This is OK until you want to call a method
in the calling object. Since self is no longer the
calling object, you have to explicitly provide the caller. Builders make
this easy by setting the +@self+ instance variable to the caller.
What happens when you need an XML tag that has the same name as an existing
method name (e.g. id )? The builder objects handle this by
inheriting from BlankSlate. In
addition, all the implementation methods in a builder begin with an
underscore, which makes them unlikely candidates for XML tags.
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.