InfoQ Homepage Articles Evaluation Options in Ruby

# Evaluation Options in Ruby

This item in chinese

### 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.

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

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:
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

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("\n")     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.

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.

Style

## Hello stranger!

You need to Register an InfoQ account or or login to post comments. But there's so much more behind being registered.

Get the most out of the InfoQ experience.

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

• ##### error

by haoxiang zhang /

• ##### Re: error

by haoxiang zhang /

• ##### error

Your message is awaiting moderation. Thank you for participating in the discussion.

Select[:column1, :column2].from[:table1, :table2].where do
equal table1.id, table2.table1_id
end

it is should be use {} instend of do ... end

because of do...end will be send to Select method , not where.

• ##### Re: error

Your message is awaiting moderation. Thank you for participating in the discussion.

sorry, it is my fault.

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

Is your profile up-to-date? Please take a moment to review and update.

Note: If updating/changing your email, a validation request will be sent

Company name:
Company role:
Company size:
Country/Zone:
State/Province/Region:
You will be sent an email to validate the new email address. This pop-up will close itself in a few moments.