Have you ever wondered how libraries like FactoryGirl (or should I say FactoryBot?) work? Or Rails routing? It all seems a bit like magic sometimes doesn’t it? Let me debunk all of that for you! These libraries are not using any tricks under hood, it’s just plain old ruby, except they’re using a few advanced aspects of the language that you might not have known about. To demonstrate that, we’ll write a simple and functional clone of FactoryGirl, Construction Girl (I couldn’t think of a other better name).
All these libraries provide you with a Domain Specific Language (DSL) that you use in order to write your programs with said libraries. Before going further it’s important to understand what a DSL is:
(…) Then there is the concept of an internal DSL, which uses the syntax of an exіsting language, a host language, such as Ruby. The means of the language are used to build constructs resembling a distinct language.
https://www.infoq.com/news/2007/06/dsl-or-not
For our purposes, every time we mention a DSL in Ruby, we’re actually talking about an internal DSL. In a nutshell, a DSL in Ruby allows you to express complex domain logic in the language using a simple syntax. Meta programming is the core Ruby feature that makes it easy to write DSL’s.
The DSL that we will build here will resemble FactoryGirl: it will create and store active record objects to the database, along with values for their columns. Let’s get to work!
Let’s assume that we have the following tables created in our database:
Owners
id
name
ageProducts
id
name
price
owner_id
product_typeA simple translation of these tables to ruby (with some additional helpers to validate the attributes) could be:
The DSL that we would like to have should work like this (the id column is not mandatory, its here as example only):
Here’s a first, if somewhat ugly, implementation of our factory:
What we’re doing here is really simple. We use pluralize in order to transform the input string (assumed to be a table name) to a pluralized version of it (so if the input is owner, it would be transformed to owners, etc), and we try to fetch the ruby class from that string (note that this is using Rails specific helpers). We then instantiate a new instance of that class, yield it if any block was given, and finally try to persist the record to the database. Here’s how we could use it:
We’re close to our goal, but this doesn’t look like the typical DSL we are used to seeing, right? We’re repeating ourselves quite often by typing the ownervariable name. How can we make this read better? We have to bring in the big gun: the powerful meta programming capabilities of ruby. Here’s the syntax that we wished we had:
In order to do this, the lines name “Michael" and age 25 must be executed in the context of a Owner object. In Ruby, we can do that by using instance_eval, which will evaluate code in the context of the receiver of the method. For example:
In this code, the line puts selfis evaluated inside the context of the receiver of the instance_eval method which, in this case, is a new instance of Owner. This is the same thing as executing puts self inside an instance method of Owner, for example:
Here’s how we can leverage this powerful feature in order to refactor the create_structure class method of the ConstructionGirl class:
With this code, the &block is executed in the context of record. But this is not enough! We need to add some additional methods in the Owner class for this to work: particularly, we need the methods name(string) and age(integer), which will set the corresponding attributes in the instance:
Let’s try our create_structure method now (I will assume that save! is a Rails ActiveRecord method to persist the record to the database):
It works! But what if we want to create the name and/or the age dynamically? For example, I would like to do this:
Which means that the methods name and age should also accept a block as argument:
In this case, the blocks are executed again in the context of self (note that we don’t pass any receiver to the instance_eval method, but, by default, ruby will see if a method instance_eval exists and can be executed in the current context, which is Owner, which is true, because instance_eval is a method allowed in any ruby object.) Now we can do fun things like:
Ok, we’re almost done. There’s one thing left in order to finish our semi functional factory girl clone: building associations. In order to build associations, we will need to change our ConstructionGirl class quite a bit, and so I’leave that for the second part of this blog post, since this one is already getting quite verbose!
Nowadays I work at Runtime Revolution. Working here has been, and continues to be, a great learning experience. I’ve matured professionally as a developer, focusing on building and maintaining large scale Ruby on Rails applications.