Skip to content

Instantly share code, notes, and snippets.

@grahamjenson
Created February 17, 2017 23:32
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save grahamjenson/147d2d7aff5e9b9a53ab43f4f6bb2fec to your computer and use it in GitHub Desktop.
Save grahamjenson/147d2d7aff5e9b9a53ab43f4f6bb2fec to your computer and use it in GitHub Desktop.

Turtles All The Way Down: Building Simple and Powerful Ruby DSLs

A Domain Specific Language (DSL) is a specialized way to clearly describe a problem domain. Ruby is a great language for creating DSLs because it gives a lot of power to developers to how the language looks so that it can more closely represent the domain. For example:

RSpec for writing tests:

https://gist.github.com/debead6ad42432f52cb19c81d9c3b8bd

FactoryGirl for mocking objects:

https://gist.github.com/d1204a863508764a33982f88b6af8395

GeoEngineer for defining cloud resources:

https://gist.github.com/759267abdf554a5be5c153f1c7047c8f

This post will briefly describe how to use Ruby to create powerful DSLs like the examples above.

Building Blocks

Ruby DSLs typically use functions that use a block given as an argument to build an instance of an domain object, e.g.:

https://gist.github.com/cdb02b818b22707d53b1248c10b0852d

Calling instance_exec(obj, block) means that self in the block is equal to obj. In this example that means self is the instance of the newly created turtle.

We can build more of the problem domain by adding methods to the base object, e.g.:

https://gist.github.com/aa10629bcf584d49796d44e450c6de3e

This is similar to the way that RSpec creates its describe and it methods. The DSL then builds a tree of domain objects, and onces defined it can be used to solve the domains problem.

Self

An annoying "gotcha" in the above DSL example is that to assign values you must call self. This is because calling name = (rather than self.name =) creates name variable scoped to the block instead of the self object. self is also really ugly, given the goal of a Ruby DSL is to hide the "cruft" that is not part of the domain.

To remove self we must use methods rather than =:

https://gist.github.com/ac809229ffdc57b9c3d8c95eb944e661

This is starting to look a bit nicer.

The Infinite DSL

Some DSLs like FactoryGirl and GeoEngineer have user defined properties that cannot be defined ahead of time. Doing this dynamically requires overriding the method_missing function:

https://gist.github.com/5ec81c078b9210029a0281834eba7020

This is the general case of assigning variables so that now any variable can be assigned. This has one significant downside, that now the turtle can never have a method missing exception, so be careful.

Lazy Evaluation

In many DSLs there are attributes that are expensive to calculate and not always needed. In GeoEngineer attributes can be lazily evaluated and then cached so only ever executed once. This is done by assigning the attribute as a Proc and executing that Proc only when it is being retrieved:

https://gist.github.com/eb548bb5e21a319dcc1b2840fb502da2

Turtles All the Way Down

Some DSLs need to be recursive, like a family tree of turtles. This can be accomplished by also handling blocks in method_missing to then create a domain object, e.g.

https://gist.github.com/ed8cfdaf274ac7b284017a108c84f025

This DSL is now capable of building complex structures of turtles, to solve the may philosophical questions they pose.

The End

Language affects the way someone thinks about a problem domain and how they search for a solution. The closer the language you use to the problem the less hurdles you have to leap to finding a solution. Building a DSL in Ruby that can represent a problem domain can help you quickly define, describe and solve problems. I like Turtles.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment