Configuration Blocks
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 p.
.
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
of the 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 useinstance_eval
on 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 self
is
the 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
the instance_eval
approach.
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
approach from
this excellent post by Michael Bleigh.