---
title: Writing a Domain-Specific Language in Ruby
teaser: |
  How to use `instance_eval` and patterns such as registries and proxies
  to create a beautiful DSL in Ruby.
tags: web,ruby
author: Gabe Berke-Williams
published_on: 2014-05-28
---

A [Domain-Specific Language], or DSL, is "a programming language of limited
expressiveness focused on a particular domain". It makes tasks in its domain
easier by removing extraneous code for a particular task and allowing you to
focus on the specific task at hand. It also helps other people read the code,
because the purpose of the code is so clear.

Here's a little Internal DSL in Ruby:

```ruby
Smokestack.define do
  factory User do
    name "Gabe BW"
    pet_name "Toto"
  end
end

user = Smokestack.build(User)
puts user.name == 'Gabe BW'  # true
puts user.pet_name == 'Toto' # true

other_user = Smokestack.build(User, name: "Bob")
puts other_user.name == 'Bob'      # true
puts other_user.pet_name == 'Toto' # true
```

Let's go over each part and how it works.

[Domain-Specific Language]: http://www.martinfowler.com/bliki/DomainSpecificLanguage.html

## Set up

First, let's define `User` and `Post` classes that we'll be using in our
factory:

```ruby
class User
  attr_accessor :name, :pet_name
end

class Post
end
```

We'll be referring to these throughout the post.

## Identifying method calls

When I first looked at code like `Smokestack.define` above, I had a hard time
identifying where methods were being called. Let's add parentheses:

```ruby
Smokestack.define do
  factory(User) do
    name("Gabe B-W")
    pet_name("Toto")
  end
end
```

That's better. Now we can see that `Smokestack.define` takes a block, and the
`factory` method takes a class, like `User`, and a block. But where is the
`factory` method coming from?

## instance\_eval

To find out, we need to look at the `instance_eval` instance method, available
on every class. According to [the docs]:

    Evaluates a string containing Ruby source code, or the given block, within the
    context of the receiver (obj). In order to set the context, the variable self is
    set to obj while the code is executing, giving the code access to obj’s instance
    variables.

Here's an example:

```ruby
class DefinitionProxy
  def factory(factory_class)
    puts "OK, defining a #{factory_class} factory."
  end
end

definition_proxy = DefinitionProxy.new
definition_proxy.instance_eval do
  factory User
  factory Post
end
```

That code prints out this:

    OK, defining a User factory.
    OK, defining a Post factory.

The `factory User` and `factory Post` are evaluated in the context of the
`definition_proxy` instance. That means that `factory User` in the
`definition_proxy.instance_eval` block is actually calling
`definition_proxy.factory(User)`.

Now let's add it to Smokestack:

```ruby
module Smokestack
  def self.define(&block)
    definition_proxy = DefinitionProxy.new
    definition_proxy.instance_eval(&block)
  end
end
```

`Smokestack.define` is now the entry point for our DSL. It takes a block, then
evaluates that block in the context of a `DefinitionProxy` instance.

[the docs]: http://ruby-doc.org/core-2.0/BasicObject.html#method-i-instance_eval

## Registering Factories

Now that our code sort of works, let's register factories. Factories aren't
helpful unless we can refer to them, and to do that we need [a central registry].
The simplest registry is a hash that maps factory classes to factories.

The following code gives us `Smokestack.registry`:

```ruby
module Smokestack
  @registry = {}

  def self.registry
    @registry
  end
end
```

Let's change the `factory` method to register factories when they're declared:

```ruby
class DefinitionProxy
  def factory(factory_class)
    factory = lambda { puts "OK, creating a #{factory_class}." }
    Smokestack.registry[factory_class] = factory
  end
end
```

Instead of printing out a message immediately, we wrap it in a `lambda`. This
means that we can store that lambda in the registry, and call factories whenever
we like after registering them:

```ruby
Smokestack.define do
  factory User
end

Smokestack.registry[User].call # => "OK, creating a User."
Smokestack.registry[User].call # => "OK, creating a User."
```

[a central registry]: http://martinfowler.com/eaaCatalog/registry.html

## Diving Deeper

Now we can declare that a factory exists, but it's not really a factory yet. It
doesn't actually initialize objects. Let's look at the original code again:

```ruby
factory User do
  name "Gabe BW"
  pet_name "Toto"
end
```

We want that code to declare a factory that does the following:

```ruby
user = User.new
user.name = "Gabe BW"
user.pet_name = "Toto"
return user
```

Like Factory Bot, we'll assume that:

* `factory User` refers to the `User` class
* The `User` class has setter methods for every attribute in the factory (e.g.
  `name=`)

Each factory might have different setter methods (e.g. `pet_name=`), so we'll
use `method_missing` to handle every case.

Let's take a stab at it:

```ruby
class Factory < BasicObject
  def initialize
    @attributes = {}
  end

  attr_reader :attributes

  def method_missing(name, *args, &block)
    attributes[name] = args[0]
  end
end

class DefinitionProxy
  def factory(factory_class, &block)
    factory = Factory.new
    if block_given?
      factory.instance_eval(&block)
    end
    Smokestack.registry[factory_class] = factory
  end
end
```

`DefinitionProxy#factory` now passes its block to a `Factory` instance, then
stores the `Factory` instance in the registry. If there's no block (i.e. if
`block_given?` evaluates to `false`), then the factory does not evaluate the
block, but is still put in the registry. Here's a case where we have no block,
because `Post` has no attributes:

```ruby
Smokestack.define do
  factory Post
end
```

`Factory` inherits from `BasicObject`, which is a class with [very few methods]
defined. It's great for use in metaprogramming like this, where you want every
instance method call to trigger `method_missing`.

We now have all of the `Smokestack.define` [DSL] working, with factories and a
registry to map factory names to stored factories. Let's add `Smokestack.build`.

[DSL]: http://martinfowler.com/books/dsl.html
[very few methods]: http://ruby-doc.org/core-2.0/BasicObject.html

## Smokestack.build

`Smokestack.build(User)` needs to do the following:

* Grab the user factory
* Set attributes on the user, with optional overrides
* Return the user

To get the attributes, we can grab `factory.attributes` from the user
factory in the registry. Let's have `Smokestack.build` take an optional second
parameter, `overrides`, which allows people to pass custom values.

Here's the code:

```ruby
module Smokestack
  def self.build(factory_class, overrides = {})
    instance = factory_class.new

    # Set attributes on the user
    factory = registry[factory_class]
    attributes = factory.attributes.merge(overrides)
    attributes.each do |attribute_name, value|
      instance.send("#{attribute_name}=", value)
    end

    # Return the user
    instance
  end
end
```

[takes a string and returns a constant]: http://ruby-doc.org/core-2.1.0/Module.html#method-i-const_get

## Putting it all together

Here's what we ended up with:

```ruby
module Smokestack
  @registry = {}

  def self.registry
    @registry
  end

  def self.define(&block)
    definition_proxy = DefinitionProxy.new
    definition_proxy.instance_eval(&block)
  end

  def self.build(factory_class, overrides = {})
    instance = factory_class.new
    factory = registry[factory_class]
    attributes = factory.attributes.merge(overrides)
    attributes.each do |attribute_name, value|
      instance.send("#{attribute_name}=", value)
    end
    instance
  end
end

class DefinitionProxy
  def factory(factory_class, &block)
    factory = Factory.new
    factory.instance_eval(&block)
    Smokestack.registry[factory_class] = factory
  end
end

class Factory < BasicObject
  def initialize
    @attributes = {}
  end

  attr_reader :attributes

  def method_missing(name, *args, &block)
    @attributes[name] = args[0]
  end
end
```

Let's run it through its paces:

```ruby
Smokestack.define do
  factory User do
    name "Gabe BW"
    pet_name "Toto"
  end
end

user = Smokestack.build(User)
puts user.name
#=> "Gabe BW"
puts user.pet_name
#=> "Toto"

other_user = Smokestack.build(User, name: 'Bob')
puts other_user.name
#=> "Bob"
puts other_user.pet_name
#=> "Toto"
```

Neat.

## How could this be made better

The use of a class directly (`factory User`) looks out-of-place if you're
familiar with Rails idioms or Factory Bot. To make this more Rails-y, we'd use
a symbol like `factory :user`.  To make this work, we can use plain Ruby or
ActiveSupport:

```ruby
# Plain Ruby
Object.const_get(:user.capitalize) #=> User

# ActiveSupport
:user.capitalize.constantize #=> User
```

## Further Reading

For more examples of DSLs, check out [factory\_bot] or the [Rails routes].

[factory\_bot]: https://thoughtbot.github.io/factory_bot/
[Rails routes]: http://guides.rubyonrails.org/routing.html

* * *

**Note:**

Looking for FactoryGirl? The library was renamed in 2017.
[Project name history can be found here.](https://github.com/thoughtbot/factory_bot/blob/master/NAME.md)
