Saturday, October 31, 2009

Recipe 10.12. Evaluating Code in an Earlier Context










Recipe 10.12. Evaluating Code in an Earlier Context





Problem


You've written a method that evaluates a string as Ruby code. But whenever anyone calls the method, the objects referenced by your string go out of scope. Your string can't be evaluated within a method.


For instance, here's a method that takes a variable name and tries to print out the value of the variable.



def broken_print_variable(var_name)
eval %{puts "The value of #{var_name} is " + #{var_name}.to_s}
end



The eval code only works when it's run in the same context as the variable definition. It doesn't work as a method, because your local variables go out of scope when you call a method.



tin_snips = 5

broken_print_variable('tin_snips')
# NameError: undefined local variable or method 'tin_snips' for main:Object

var_name = 'tin_snips'
eval %{puts "The value of #{var_name} is " + #{var_name}.to_s}
# The value of tin_snips is 5





Solution


The eval method can execute a
string of Ruby code as though you had written in some other part of your application. This magic is made possible by Binding objects. You can get a Binding at any time by calling Kernel#binding, and pass it in to eval to recreate your original environment where it wouldn't otherwise be available. Here's a version of the above method that takes a Binding:



def print_variable(var_name, binding)
eval %{puts "The value of #{var_name} is " + #{var_name}.to_s}, binding
end

vice_grips = 10
print_variable('vice_grips', binding)
# The value of vice_grips is 10





Discussion


A Binding object is a bookmark of the Ruby interpreter's state. It tracks the values of any local variables you have defined, whether you are inside a class or method definition, and so on.


Once you have a Binding object, you can pass it into eval to run code in the same context as when you created the Binding. All the local variables you had back then will be available. If you called Kernel#binding within a class definition, you'll also be able to define new methods of that class, and set class and instance variables.


Since a Binding object contains references to all the objects that were in scope when it was created, those objects can't be garbage-collected until both they and the Binding object have gone out of scope.




See Also


  • This trick is used in several places throughout this book; see, for example, Recipe 1.3, "Substituting Variables into an Existing String," and Recipe 10.9, "Automatically Initializing Instance Variables"













No comments: