Many times in Ruby, it is useful to allow the creator of an object to configure it using a block. This is especially true when writing a domain-specific language (DSL).
At work, I’ve built a Ruby DSL on top of Rake for building our C++ applications, and I use the configuration block idiom to allow a particular project to be customized:
To support this API, I have code something like:
This works fine, but I find the configuration block to be a bit more verbose than I’d like. There is a way to simplify the block:
In this version, I no longer need to specify the
p argument to the
block or prefix all of the configuration method calls with
The code to make this work reaches deeper into the bag of Ruby tricks:
This version uses
instance_eval to evaluate the block in the context
Project instance being configured. Any implicit references
to self within the block are referring to that instance.
This makes the configuration block cleaner and more readable, especially in the context of a DSL. But there are (at least) two potential issues with it:
The configuration block has access to any method, public or private, defined on
Project. In this case I’m OK with that, but it might be an issue in other circumstances. If I was worried about it, I would introduce a separate configuration object that has only the desired API and use
instance_evalon that object instead.
The configuration block cannot use any methods local to the calling context. For example:
This doesn’t work, because the block is evaluated such that
Project instance, and so can’t see the local
libraries_to_link_with method. In this case, the client really
needs to use the original block syntax with the explicit argument:
Since we changed our implementation, though, this option is no longer available. The solution is to make the configuration block handling code more flexible:
Now, we look at the block to see if it needs an argument or not. If
it does, we use the original
yield self approach; otherwise, we use
This gives the client code the option of which form to use. In the normal case, no block argument is required and the client can use the cleaner form of configuration block. However, if the client needs to do something more complex, the original form is still available as well.
I learned about both the
instance_eval approach and the flexible
this excellent post by Michael Bleigh.