In Ruby, is there a Binding available that is created at the time of the exception available during the rescue?

79 Views Asked by At

The precise pain point I'm having is I am parsing a list of files. Each file has multiple lines. When something goes wrong, I'd like to print out such things as the current file name, the current line number, the current line, and some other interesting variables and then exit in most cases since the error will be in my code that needs to be enhanced.

Most of the variables are local to the #each blocks that are nested. There are multiple places something could go wrong.

What I'd like to do is just have one global begin, rescue, end block and within the rescue have a Binding available to me so I could dig out the various variables I'm interested in printing out.

This seems like a rather obvious Ruby type thing so I find it odd that I'm the first guy who would want such a thing. Yet, I don't see any Binding or closure concepts in the exception handling parts of Ruby's documentation. Usually this means I'm radically misusing the concepts of the language.

1

There are 1 best solutions below

1
Leon On

Well, the easiest way is to define all variables that you want to track before your begin ... rescue ... end block.

You can then easily access them within the rescue block:

variable = 0
another_variable = 0
begin
    3.times do |a|
        variable = a
        5.times do |b|
            another_variable = b
            if a == 2 and b == 4
                raise KeyError
            end
        end
    end
    at_exit do
        puts binding.eval 'variable'
        puts binding.eval 'another_variable'
    end
rescue KeyError
    puts variable
    puts another_variable
end

But if you want to get last binding before exception occurs - your solution is to use TracePoint: https://ruby-doc.org/core-2.5.0/TracePoint.html

This allows u to track bindings where local variable that u need is defined and then get last of them. Of course, if you will use this method it will make your program a little bit slower. The example of usage:

last_binding = nil
trace = TracePoint.new(:b_return) do |tp|
  last_binding = tp.binding if tp.binding.local_variable_defined?('variable')
end

trace.enable

begin
    3.times do |a|
        variable = a
        5.times do |b|
            another_variable = b
            if a == 2 and b == 4
                raise KeyError
            end
        end
    end
    at_exit do
        puts binding.eval 'variable'
        puts binding.eval 'another_variable'
    end
rescue KeyError
    trace.disable
    puts last_binding.eval 'another_variable'
end

(The best event for u to track is b_return - it occurs after each block's ending, so this will make your tracking time-optimal)