Demystifying Ruby DSLs — Part 2

What helped me ultimately understand how these things work is that you are building up classes on the fly. It’s kind of like when you include a module, it’s editing that class to add more methods to it. Think of a bunch of Legos. Each Lego is a module with various methods on it. At runtime they assemble together to build a castle.

Dynamically Adding

What if those Legos could generate even more Legos as you were building with them, and then intelligently join themselves together?

If you’re familiar with Rails, you’ve seen that you can just declare associations within a model — :has_many, :has_one, and so on. Once you add those directives to your class, suddenly you have access to brand spanking new methods. Have you ever wondered how that worked? Let’s implement a rudimentary version.

Think for a moment about :has_many. What would you expect the line has_many :gerbils methods to do? You would have to have a gerbils method to retrieve the little fellas, and another one, gerbils=(new_value) to set them (and others to add them and so on, but KISS). You implement that with a generic get_child_models(child_name) method, but that feels like the Java (™ Oracle Corporation) way… and I have too much self respect to go down that path. Instead we can take advantage of Ruby’s metaprogramming capabilities and generate them dynamically.

One way to do this is with eval.

A Word on Eval

Ruby has a few versions of eval. They all take strings or blocks and turn them in code that is executed.

Now, if you include Associations in your class, you can call has_many :hamsters or has_many :gerbils or has_many :guinea_pigs and have all of your getters and setters created.

This is you with all the gerbil methods.

Caution

I’m not a big fan of eval, at least when using it with strings. The biggest reason is that it makes bugs harder to find. The Ruby interpreter will point out syntax errors when the file loads, but a typo in an evalled string won’t get caught until runtime. The longer the string, the more likely something bad will creep in there. And some of these dynamically created methods will be long. I’m talking Lord of the Rings Extended Edition long.

Fortunately there is a better way. The eval methods also take blocks, which work pretty well in most cases. For the purposes of dynamically generating methods, I prefer using define_methodsource. It’s available on Module (and therefore classes too) and, just like it says on the tin, is designed to create methods on the fly and add them to a class.

It’s kinda similar to the eval code, in fact, define_method passes itself along to instance_eval, so when all is said and done, it’s merely for our convenience. But is easier to test, and will complain loudly if there’s a syntax error.

Working with actual code rather than a string makes refactoring easier too. Let’s say you want to enable your users to define their own implementations of the generated rodent methods. Pulling that out into its own method is simple:

That’s still possible with string evals, but is easier to read in my eyes.

That’ll wrap up this entry on DSLs. There’s only one other big piece of the pie I’d like to cover — blocks, but you can do a whole lot without them.

One thing to keep in mind when writing DSLs is that it can be hard to follow along. Document everything, especially the esoteric parts. It might even be a good idea to diagram the path of all the include chain. DSLs can make client code easier to write, but usually at the expense of crazy complexity within the DSL itself.