14.1. Eval and Bindings

Many scripting languages have a facility to allow an arbitrary string to be executed at runtime. In Ruby, that feature comes from the eval method. Here's an example of that method:

>> eval("2 + 2")
=> 4

By default, variables in the code string are evaluated in the current context, as follows:

>> x = 3
=> 3
>> y = 5
=> 5
>> eval("x * y")
=> 15

However, you can evaluate the string in a more-or-less arbitrary environment by passing in an optional second argument, which must be a Ruby binding. Bindings are kind of odd entities that encapsulate the context of a block, which is basically a lookup table that stores the values of all accessible variables and constants. From the binding, the state of variable values when the block is created can be revisited.

Every block or Proc object defines its own binding, within which it can set local variable values. Under normal circumstances, this binding is accessed only when the block is executed, to determine the values of variables used by the block. However, you can retrieve just the binding and execute an arbitrary statement within it using the variable values as defined when the block was created.

Here's a test script that shows how bindings work:

def some_crazy_thing
  a = 10
  b = 20
  Proc.new {c = 10}
end

p eval("a * b", some_crazy_thing.binding)

The method some_crazy_thing sets a couple of variables and returns a Proc object. The last line of the script takes that block, grabs its binding, and tries to add two variables. When run, a and b are evaluated in the binding of the Proc object returned by some_crazy_thing, with this result:

>>> eval_binding.rb

200

Even though the actual block does not contain the variables a and b, both are accessible from the block because they were in scope when the block was created. The block itself is never executed.

By using a binding argument, you are re-entering the variable context as it existed when the block was created — any variable that was available to be used inside the block can also be referenced inside the eval string. On the other hand, the binding doesn't know or care what variables are actually created inside the block because the block is never run. Those values are not available to the binding and cannot be referenced inside the eval string. In other words, placing the following line at the bottom of the preceding script would result in an error:

p eval("a * c", some_crazy_thing.binding)

Because c is defined only inside the block and is not accessible at the time the block was created, it cannot be referenced in the eval string. You can, however change the values of a variable that is then used in the block, like this:

def another_crazy_thing
  a = 10
  Proc.new {a * a}
end

proc = another_crazy_thing
eval("a = 5", proc.binding)
p proc.call

This code snippet returns 25, because the eval changes the value of a in the binding, and therefore in the block execution context as well. It seems, though, that you are limited to changing the value of existing variables in the binding, you can't add a new variable to the scope.

Ruby offers a couple of other ways to choose an execution context for an eval string. Probably the most important is instance_eval, shown in the following code:

>> r = Recipe.find(1)
=> #<Recipe id: 1, title: "Grandma's Chicken Soup", servings: "3", 
description: "Yummy!", directions: "Things", created_at:
"2007-08-05 20:03:33", updated_at: "2007-09-25 00:55:17", user_id: 2,
cached_tag_list: "grandma, chicken, yummy", cached_ingredient_string: "2 cups stock 2 cups stock 1/2 oz. carrot 1/2 oz. ca...", soup_image_id: 1> >> r.instance_eval("title") => "Grandma's Chicken Soup"

The instance_eval method uses the method receiver instance as the context for evaluating the string. Specifically, the code string will be evaluated as though the self object is implicitly set to the receiver of the instance_eval. In this example, the simple string title is evaluated as though it was r.title. Instead of calling instance_eval with a string, you can also call it with a block, like this:

>> r.instance_eval do
?> title
>> end
=> "Grandma's Chicken Soup"

The contents of the block are then evaluated within the receiving object's context.

Ruby also has analogous methods called class_eval and method_eval. You can use class_eval as one of several mechanisms to dynamically add instance methods to a class at runtime. For example:

class EvalTest

end

EvalTest.class_eval do
  def hello
    p "hi there"
  end
end

test = EvalTest.new
test.hello

In this example, the class_eval method evaluates its block in the context of the EvalTest class. Within that block, a method is defined, so when the class_eval is executed, that method definition is evaluated in the context of that class, adding the instance method hello to EvalTest. Note that the method is added as an instance method because the evaluation is done in the class context. If you want to use eval to add a method as a class method, then you need to use instance_eval, like so (but I'll show you a better way of adding class methods in a moment):

EvalTest.instance_eval do
  def good_bye
    p "so long"
  end
end
EvalTest.good_bye

And this is where metaprogramming can start to get a little headache-inducing. Calling class_eval creates an instance method, while calling instance_eval creates a class method (technically, an instance method of the class). This is all perfectly logical within the counterintuitive world of treating classes as instances in their own right. More on this when we discuss singleton classes in a couple of sections.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.15.218.133