Class | Binding |
In: |
lib/extensions/binding.rb
|
Parent: | Object |
Ruby‘s built-in Binding class doesn‘t contain any methods. It is merely a "context" object that can be used in calls to Kernel.eval, like this:
def example(_binding) return eval("x", _binding) end x = 55 current_binding = Kernel.binding example(current_binding) # -> 55
The most useful method introduced to Binding by the extensions package is Binding.of_caller. It allows you to access the binding of the calling method, thus enabling you to access local variables in that scope. The other methods are a convenient object-oriented facade for operations that you can already do with eval as demonstrated above. Here is an example that showcases all of the Binding methods included in extensions.
def example Binding.of_caller do |b| puts "x + y = #{b.eval('x + y')}" puts "x = #{b[:x]}" puts "Local variables: " + b.local_variables.join(', ') b[:y] += 1 puts "Changed value of y in calling context to #{b[:y]}" puts "Is 'z' defined in calling context? " + (b.defined?(:z) ? 'Yes' : 'No') end end x = 5 y = 17 example y # -> 18
Binding.of_caller was written by Florian Gross. The other methods were written by Tom Sawyer.
This method returns the binding of the method that called your method, enabling you to access its local variables. If you call it without being in a method, it will raise an Exception.
def inc_counter Binding.of_caller do |b| eval("counter += 1", b) end # <--- line (A) end counter = 0 inc_counter inc_counter counter # -> 2
Binding.of_caller must be the last method call in the method. For example, if you insert some code at line A in the example above, an Exception will be raised. You‘ll get away with a simple assignment, but anything involving a method call is trouble.
It works by installing a temporary trace_func (see Kernel.set_trace_func). This makes available — to the trace function — the binding of a method after it has returned. Using a continuation, Binding.of_caller will let your method return, retrieve the binding, and return to the of_caller call with that binding in hand. This time it executes the block.
Because it is actually running Binding.of_caller twice, and returning from your method twice, any code between the of_caller call and the end of your method will be run twice. This is obviously not desirable, so an Exception is raised if any code is found.
See the thread around ruby-talk:109607 for more discussion.
If you have a trace function in place, Binding.of_caller will destroy that. Ruby does not allow you to access the current trace function, so it can‘t be restored afterwards. XXX: will this clash with the profiler and/or debugger?
Binding.of_caller was written by Florian Frank.
# File lib/extensions/binding.rb, line 107 def Binding.of_caller(&block) old_critical = Thread.critical Thread.critical = true count = 0 cc, result, error = Continuation.create(nil, nil) error.call if error tracer = lambda do |*args| type, context = args[0], args[4] if type == "return" count += 1 # First this method and then calling one will return -- # the trace event of the second event gets the context # of the method which called the method that called this # method. if count == 2 # It would be nice if we could restore the trace_func # that was set before we swapped in our own one, but # this is impossible without overloading set_trace_func # in current Ruby. set_trace_func(nil) cc.call(eval("binding", context), nil) end elsif type != "line" set_trace_func(nil) error_msg = "Binding.of_caller used in non-method context or " + "trailing statements of method using it aren't in the block." cc.call(nil, lambda { raise(Exception, error_msg ) }) end end unless result set_trace_func(tracer) return nil else Thread.critical = old_critical yield result end end
Returns the value of the given variable in this binding.
# File lib/extensions/binding.rb, line 188 def [](variable) self.eval(variable.to_s) end
Sets the given variable (in this binding) to the given value.
# File lib/extensions/binding.rb, line 203 def []=(variable, value) self.eval("lambda { |v| #{variable} = v }").call(value) end
Evaluates the given string in the context of this binding.
# File lib/extensions/binding.rb, line 158 def eval(str) Kernel.eval(str, self) end