Class: Binding

Inherits:
Object
  • Object
show all
Defined in:
lib/binding_of_caller.rb

Overview

for RDoc

Class Method Summary collapse

Class Method Details

.of_caller(&block) ⇒ Object

This method returns the binding of the method that called your method. It will raise an Exception when you’re not inside a method.

It’s used like this:

def inc_counter(amount = 1)
  Binding.of_caller do |binding|
    # Create a lambda that will increase the variable 'counter'
    # in the caller of this method when called.
    inc = eval("lambda { |arg| counter += arg }", binding)
    # We can refer to amount from inside this block safely.
    inc.call(amount)
  end
  # No other statements can go here. Put them inside the block.
end
counter = 0
2.times { inc_counter }
counter # => 2

Binding.of_caller must be the last statement in the method. This means that you will have to put everything you want to do after the call to Binding.of_caller into the block of it. This should be no problem however, because Ruby has closures. If you don’t do this an Exception will be raised. Because of the way that Binding.of_caller is implemented it has to be done this way.

Please note that currently bindings returned by Binding.of_caller() will have a wrong self context which means you can not call methods, access instance variables and so on on the calling object. You can work around this by defining the method which uses the binding on all objects and telling your users to use them without a receiver. This is how ruby-breakpoint works around the problem.

This is believed to be a bug in Ruby and has been reported to ruby-core. See www.ruby-forum.com/topic/67255



46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/binding_of_caller.rb', line 46

def Binding.of_caller(&block)
  old_critical = Thread.critical
  Thread.critical = true
  count = 0
  cc, result, error, extra_data = Continuation.create(nil, nil)
  if error then
    Thread.critical = old_critical
    error.call
  end

  tracer = lambda do |*args|
    type, context, extra_data = args[0], args[4], args
    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(context, nil, extra_data)
      end
    elsif type == "line" then
      nil
    elsif type == "c-return" and extra_data[3] == :set_trace_func then
      nil
    else
      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(ArgumentError, error_msg) }, nil)
    end
  end

  unless result
    set_trace_func(tracer)
    return nil
  else
    Thread.critical = old_critical
    case block.arity
      when 1 then yield(result)
      else yield(result, extra_data)        
    end
  end
end