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.

Methods

[]   []=   defined?   eval   local_variables   of_caller  

Public Class methods

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.

Example

  def inc_counter
    Binding.of_caller do |b|
      eval("counter += 1", b)
    end
    #              <--- line (A)
  end
  counter = 0
  inc_counter
  inc_counter
  counter           # -> 2

Warning

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.

Explanation

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.

Extra Warning

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?

Credits

Binding.of_caller was written by Florian Frank.

[Source]

# 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

Public Instance methods

Returns the value of the given variable in this binding.

[Source]

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

[Source]

# File lib/extensions/binding.rb, line 203
    def []=(variable, value)
      self.eval("lambda { |v| #{variable} = v }").call(value)
    end

Evaluates defined? in this binding.

[Source]

# File lib/extensions/binding.rb, line 218
    def defined?(variable)
      self.eval("defined?(#{variable})")
    end

Evaluates the given string in the context of this binding.

[Source]

# File lib/extensions/binding.rb, line 158
    def eval(str)
      Kernel.eval(str, self)
    end

Returns the variables that are local to this binding.

[Source]

# File lib/extensions/binding.rb, line 173
    def local_variables
      self.eval('local_variables')
    end

[Validate]