Build your own Domain-Specific Language with Ruby
--
“So, what is your favourite design pattern?” As odd as it sounds, it is a common question between fellow engineers and in job interviews.
But aren’t we supposed to weigh our options according to our application logic and pick a suitable one? Leaving that aside, my answer would probably be internal DSLs.
Arguably, one of the most famous books written on this subject is Design Patterns: Elements of Reusable Object-Oriented Software by the ”Gang of Four”. However, you won’t find DSLs in this book, and which is why exactly it is interesting. Most of the original design patterns described in this book were developed without Ruby in mind. By leveraging Ruby’s features, DSL provides a way to implement our own little language.
When writing Ruby code, we use internal DSLs quite often. Rake, RSpec, ActiveRecord and Sinatra come with their own internal DSL. Let’s examine two common testing libraries among Rubyists: minitest and RSpec.
The former minitest example uses pure Ruby, our PlaneTest
class inherits from MiniTest::Test
and we create instance methods for each test. On the RSpec example, we use the two methods describe
and it
for the same purposes. Under the hood, RSpec uses a DSL to allow this syntax that looks like plain English.
Simplistic DSL syntax is enabled by a couple of features in Ruby:
- Omitting parentheses on method invocations:
eval
method evaluates the argument and runs the content as Ruby program text.
instance_eval
will go one step further and change the context(self
) to the object thatinstance_eval
is being called on.
Now that we have covered the basic principles, let’s implement our own little JSON parser that iterates over a collection of objects that have similar structure and extracts a Ruby array with the selected properties. It can be useful when dealing with large JSON files. The below example is from Yahoo’s woeid locations collection:
Our JSONParser
class with on_path
and extract
methods to setup the parser:
We can pass in a block with the relevant method calls while creating a new JSONParser
:
We can use instance_eval
to get rid of the need of calling methods on our parser object by changing our initialize
method:
With our new initialize
and by omitting parentheses our parser is much more DSL-like now:
DSLs are great when dealing with an isolated problem repeatedly. Now that we’ve developed one of our own, we know what is going on in the background when we come across one.
For more on Ruby and DSLs: