Introduction

One of my favorite features of Ruby is the ability to evaluate a string or a block of code. Ruby provides a few different types of evaluation options; however, the evals I use most often are: eval, instance_eval, and class_eval

Module.class_eval

The class_eval (and the aliased module_eval) method of Module allows you to evaluate a string or block in the context of a class or module.

Common usages for class_eval include adding methods to a class and including other modules in a class.

Related Sponsored Content 3 Common Pitfalls in Microservice Integration – And How to Avoid Them

klass = Class.new

klass.class_eval do

include ERB::Util



def encoded_hello

htnl_escape "Hello World"

end

end



klass.new.encoded_hello #=> <b>Hello World</b>

The above behavior can be achieved without using class_eval; however, a fair amount of readability must be traded.

klass = Class.new

klass.send :include, ERB::Util

klass.send :define_method, :encoded_hello do

html_escape "Hello World"

end

klass.send :public, :encoded_hello



klass.new.encoded_hello #=> <b>Hello World</b>

Object.instance_eval

The instance_eval method of Object allows you to evaluate a string or block in the context of an instance of a class. This powerful concept allows you to create a block of code in any context and evaulate it later in the context of an individual instance. In order to set the context, the variable self is set to the instance while the code is executing, giving the code access to the instance's variables.

class Navigator

def initialize

@page_index = 0

end

def next

@page_index += 1

end

end

navigator = Navigator.new

navigator.next

navigator.next

navigator.instance_eval "@page_index" #=> 2

navigator.instance_eval { @page_index } #=> 2

Similar to the class_eval example, the value of an instance variable can be gotten in other ways; however, using instance_eval is a very straightforward way to accomplish the above task.

Kernel.eval

The eval method of Kernel allows you to evaluate a string in the current context. The eval method also allows you to optionally specify a binding. If a binding is given, the evaluation will be performed in the context of the binding.

hello = "hello world"

puts eval("hello") #=> "hello world"



proc = lambda { hello = "goodbye world"; binding }

eval("hello", proc.call) #=> "goodbye world"

Expanding the context of eval

My first usage of eval was when I created the attr_init class method. I found that I was often repeating the following pattern in the code.

def some_attribute

@some_attribute || = SomeClass.new

end

I decided to create a class method that would encapsulate the above behavior.

class << Object

def attr_init(name, klass)

define_method(name) { eval "@#{name} ||= #{klass}.new" }

end

end

I remember feeling like the call to eval was ugly, but at the time I couldn't think of another way to complete the task. I posted the code on my blog for all to criticize, and they quickly did. It was not obvious at first to me, but the following solution is far superior since it only requires one call to eval instead of a new eval with each call to the defined method.

class << Object

def attr_init(name, klass)

eval "define_method(name) { @#{name} ||= #{klass}.new }"

end

end

This optimization is interesting in that it requires increasing what is evaluated for better performance. From that point forward I used eval when necessary; however, I was careful to keep an eye toward using it as efficiently as possible.

Using instance_eval in multiple contexts

It can often be valuable to evaluate blocks (or code as strings) in various contexts. This is a very common technique used when designing a Domain Specific Langauges (DSL). In fact, one key to using a DSL is the ability to evaluate in various contexts. Take this code for example:

class SqlGenerator

class << self

def evaluate(&script)

self.new.instance_eval(&script)

end

end



def multiply(arg)

"select #{arg}"

end



def two(arg=nil)

"2#{arg}"

end



def times(arg)

" * #{arg}"

end

end

The above code allows you to generate a SQL statement by calling the SqlGenerator.evaluate method with a block:

SqlGenerator.evaluate { multiply two times two }

=> "select 2 * 2"

However, you could also execute the same code in the context of a calculator class to receive a result:

class Calculator

class << self

def evaluate(&script)

self.new.instance_eval(&script)

end

end



def multiply(arg)

eval arg

end



def two(arg=nil)

"2#{arg}"

end



def times(arg)

" * #{arg}"

end

end

Which executes as:

Calculator.evaluate { multiply two times two }

=> 4

The above code demonstrates how to use instance_eval to specify the scope in which a block executes. As I previously documented, the instance_eval method evaluates either a string or block within the context of the receiver. The receivers in my examples were a new instance of SqlGenerator and a new instance of Calculator. Also, be sure to note that I call self.new.instance_eval. If I don't call the new method of self, the block will be evaluated by the class and not an instance of the class.

The above code also shows the first few steps towards defining a DSL. Creating an DSL is challenging, but it does provide many advantages. One advantage to expressing your business rules in a DSL is the ability to execute them in various contexts. As the above example shows, by executing the DSL in various contexts you can generate multiple behaviors from the same business rule. When the rule changes over time, all parts of the system that reference the rule will also be changed. The key to achieving this success is taking advantage of Ruby's evaluation methods.

The Casino Poker Table example

if the '$5-$10 Limit' list is more than 12 then notify the floor to open

if the $1-$2 No Limit' list is more than 15 then notify the floor to open

if the '$5-$10 Limit' list is more than 8 then notify the brush to announce

if the '$1-$2 No Limit' list is more than 10 then notify the brush to announce

Using the evaluation options it is easy to execute in various contexts. For example, assume you work for a casino and you have been tasked with designing a system that will notify the poker room employees when a new poker table needs to be opened or when you are looking to open a new poker table. The rules for opening a poker table vary based on the stakes of the table and the length of the waiting list. For example, you need more people waiting for a no-limit game because people are more likely to lose all their money on one hand and you don't want to open the table and need to close it shortly after because there aren't enough players. The rules would be expressed in your DSL like this:

The first context in which I will execute the DSL is the context that notifies the employees.

class ContextOne < DslContext



bubble :than, :is, :list, :the, :to



def more(value)

'> ' + value.to_s

end



def method_missing(sym, *args)

@stakes = sym

eval "List.size_for(sym) #{args.first}"

end



def floor(value)

__position(value, :floor)

end



def brush(value)

__position(value, :brush)

end



def open

__action(:open)

end



def announce

__action(:announce)

end

def __action(to)

{ :action => to }

end



def __position(value, title)

value[:position] = title

value

end



def notify(value)

[@stakes, value]

end



end

ContextOne is executed by the following code.

script = <<-eos if the '$5-$10 Limit' list is more than 12 then notify the floor to open

if the '$1-$2 No Limit' list is more than 15 then notify the floor to open

if the '$5-$10 Limit' list is more than 8 then notify the brush to announce

if the '$1-$2 No Limit' list is more than 10 then notify the brush to announce eos

class Broadcast

def self.notify(stakes, options)

puts DslContext.sym_to_stakes(stakes)

options.each_pair do |name, value|

puts " #{name} #{value}"

end

end

end



ContextOne.execute(script) do |notification|

Broadcast.notify(*notification)

end

ContextOne inherits from DslContext. The definition of DslContext can be found below.

class DslContext

def self.execute(text)

rules = polish_text(text)

rules.each do |rule|

result = self.new.instance_eval(rule)

yield result if block_given?

end

end



def self.bubble(*methods)

methods.each do |method|

define_method(method) { |args| args }

end

end



def self.polish_text(text)

rules = text.split("

")

rules.collect do |rule|

rule.gsub!(/'.+'/,extract_stakes(rule))

rule << " end"

end

end



def self.extract_stakes(rule)

stakes = rule.scan(/'.+'/).first

stakes.delete!("'").gsub!(%q{$},'dollar').gsub!('-','dash').gsub!(' ','space')

end



def self.sym_to_stakes(sym)

sym.to_s.gsub!('dollar',%q{$}).gsub!('dash','-').gsub!('space',' ')

end

end

The method_missing method of ContextOne also expects the following List class to be defined.

class List

def self.size_for(stakes)

20

end

end

ContextOne uses the DSL to check the List for the size per stakes and sends notifications when necessary. This is of course sample code and my List object is just a stub to verify that everything works correctly. The important thing to note is that the execute method is delegating to instance_eval to evaluate the code in the context of ContextOne.

Based on this same script you could execute a second context that returns a list of the different games that are currently being spread.

class ContextTwo < DslContext



bubble :than, :is, :list, :the, :to, :more, :notify, :floor, :open, :brush



def announce

@stakes

end



alias open announce



def method_missing(sym, *args)

@stakes = sym

end



end

As you can see, adding additional contexts is very easy. Since the execute method of DslContext uses the instance_eval method the above code can be executed with the code below.

ContextTwo.execute(script) do |stakes|

puts ContextTwo.sym_to_stakes(stakes)

end

To drive the example home we will create another example to display all positions that are set up to receive notices.

class ContextThree < DslContext



bubble :than, :is, :list, :the, :to, :more, :notify, :announce, :open, :open



def announce; end

def open; end



def brush(value)

:brush

end



def floor(value)

:floor

end



def method_missing(sym, *args)

true

end



end

Again, the context inherits from DslContext, which uses instance_eval, so the only code necessary to execute is the following code.

ContextThree.execute(script) do |positions|

puts positions

end

Being able to evaluate a DSL script in multiple contexts begins to blur the line between code and data. The script 'code' can also be evaluated to do things such as generate reports (i.e. A report of which employees are contacted by the system). The script could also be evaluated in a context that will show how long before a table will be opened (i.e. the rule states that 15 are needed, the system knows 10 are on the list so it displays the message '5 more people needed before the game can start'). Using instance_eval allows us to evaluate the same code in any way necessary for our systems.

Eval is magic also

The above examples show how to evaluate blocks in different scopes by using instance_eval. But, eval also allows you to evaluate in more than one context. Next I'll show how you can evaluate a string of ruby code in the scope of a block.

At the beginning we started with a simple example, but lets revist a simple case where we use eval with the binding of a block. For this example we will need a class that can create a block for us.

class ProcFactory

def create

Proc.new {}

end

end

In the example, the ProcFactory class has a method, create, that simply creates and returns a proc. Despite the fact that this doesn't look very interesting, it actually provides you with the ability to evaluate any string of ruby code in the scope of the proc. This gives you the power to evaluate ruby code in the context of an object without directly having to reference it.

proc = ProcFactory.new.create

eval "self.class", proc.binding #=> ProcFactory

When would you ever actually use such a thing? I recently used it while developing a DSL to represent SQL. I started with the following syntax.

Select[:column1, :column2].from[:table1, :table2].where do

equal table1.id, table2.table1_id

end

When the above code is evaluated the [] instance method (following from) saves each table name in an array. Then, when the where method is executed it yields the block given to where. When the block is yielded the method_missing method is called twice, once with :table1 and then with :table2. When method_missing is called we check that table name array (which was created in the previously mentioned [] method) includes the symbol argument (:table1 and :table2) to verify it is a valid table name. If the table name is included in the table name array we return an object that knows how to react to column names. However, if the table name is invalid we call super and raise NameError.

All of this works perfectly, until you start using sub-queries. For example, the following code will not work with the current implementation.

Delete.from[:table1].where do

exists(Select[:column2].from[:table2].where do

equal table1.column1, table2.column2

end)

end

Unfortunately, this needed to work, and using eval and specifying a binding was how we made it work. The trick is getting the table names array from the outer block into the inner block without explicitly passing it in somehow (which would have made the DSL ugly).

To solve this problem, within the where method of the Select class we used the binding of the block to get the tables collection from the Delete instance. This is possible because the where method of the Delete instance is the context aka the binding of the block being passed to the where method of the select instance. The binding (or context) is the scope in which the block is created. The following code is the full implementation of the where method.

def where(&block)

@text += " where "

tables.concat(eval("respond_to?(:tables) ? tables : []",

block.binding)).inspect

instance_eval &block

end

Let's break the eval statement apart and see what it's doing. The first thing it's going to do is

eval "respond_to?(:tables) ? tables : []", block.binding

Which simply means "eval that statement within the scope of the block". In this case the scope of that block is:

Delete.from[:table1].where do .. end

This scope is a Delete instance, which does have a tables method that exposes the tables array (tables #=> [:table1]). Therefore, the statement will evaluate and return the tables array, and the rest of the statement could be pictured as this:

tables.concat([:table1])

This is simply going to concatenate all the table names into the tables array available in the inner block. After this one line change, the statement now produces expected results.

delete from table1 where exists (select column2 from table2 where table1.column1 = table2.column2)

The following code can be used to generate the above result and can be used as a reference for using a binding with eval.

class Delete

def self.from

Delete.new

end



def [](*args)

@text = "delete from "

@text += args.join ","

@tables = args

self

end



attr_reader :tables



def where(&block)

@text += " where "

instance_eval &block

end



def exists(statement)

@text += "exists "

@text += statement

end

end



class Select

def self.[](*args)

self.new(*args)

end



def initialize(*columns)

@text = "select "

@text += columns.join ","

end



def from

@text += " from "

self

end

def [](*args)

@text += args.join ","

@tables = args

self

end



def tables

@tables

end



def where(&block)

@text += " where "

tables.concat(eval("respond_to?(:tables) ? tables : []", block.binding)).inspect

instance_eval &block

end



def method_missing(sym, *args)

super unless @tables.include? sym

klass = Class.new

klass.class_eval do

def initialize(table)

@table = table

end



def method_missing(sym, *args)

@table.to_s + "." + sym.to_s

end

end

klass.new(sym)

end



def equal(*args)

@text += args.join "="

end

end

Conclusion

As you can see the evaluation methods of Ruby provide very good options for creating concise, readable code. The evaluation methods also provide the ability to easily create very powerful tools such as Domain Specific Languages.

About the author

Jay Fields is a software developer at ThoughtWorks. He is a early adopter who is consistantly looking for new exciting technologies. His most recent work has been in the Domain Specific Language space where he delivered applications that empowered subject matter experts to write the business rules of the applications.