What is a DSL?
A DSL, or Domain Specific Language, is a language that has a specific purpose rather than a general purpose like C or Java. One of the most popular DSLs is SQL because it’s the standard for querying a relational database and it’s syntax is specific to activities such as sorting, filtering and displaying data.
SQL falls under the category of external DSLs which means it requires its own parser and implementation of all of the necessary components. Other examples of external DSLs are Gherkin, for writing feature files, Make Files, for building C and C++ applications and HTML, for declaring webpages).
What is a Ruby DSL?
Ruby is a powerful and expressive language that can be leveraged to create an internal DSL. Instead of requiring a full-syntax parser and an underlying implementation, metaprogramming techniques can be used to create a DSL that leverages the Ruby language.
Anyone that has used a popular Ruby framework has most likely seen a Ruby DSL. Frameworks that leverage these include:
- Rake
- RSpec
- Active Record
- Sinatra
Before RSpec came along, TestUnit was the most popular testing framework in Ruby. A test looked a lot like a typical Ruby class:
RSpec changed the way programmers write test cases by creating an internal DSL that is much more expressive.
Creating a simple Ruby DSL:
In this tutorial we’ll create a DSL for querying a CSV file.
The full file can be found here.
The end result will be a Ruby DSL that reads like this:
Step 1: Create a standard Ruby class
Look at the plain old Ruby code from the following branch here.
The class is as follows:
This can be used just like any other Ruby object:
Step 2: Implementing method_missing
Method missing is the first technique we will use to build our DSL,so paste the following into a IRB console:
We’ll now override the default behaviour by implementing method_missing to bring the error up on the screen:
Now that we’ve changed the default behaviour we can leverage this to create our Ruby DSL. Add the following to the ç class in the world_cup_dsl.rb file:
As you can see, we can now use a method called country, or any other row heading in the CSV such as position or last_name,to filter the the data by column value.
The full code for this part can be seen here.
Step 3: Implementing instance_eval
A Proc in Ruby is Ruby code that is declared in a block and assigned to a variable to be used at a later time. Try the following in IRB:
Now let us change the implementation to use a method call foo instead of puts
You will get an error: NoMethodError: undefined method `foo' for main:Object
This is where our second metaprogramming technique will be used. We will use instance_eval to call this proc but in a different way with an object that has the foo method.
By using instance_eval the code block is run in a different way where the foo method exists.
With this extra functionality we can now execute WorldCupDSL in a way that is similar to RSpec:
You can see the final product here.