1

I want to start a Pry session in a function, but due to some meta-programming in the project I'm now working on, Pry gets triggered in a lot of contexts that I'm not interested in. How can I trigger pry based on the stack-trace of the program's current execution?

Something like,

binding.pry if stacktrace.include? "function_name"

2 Answers2

2

For Ruby 2.0+, you can use Kernel#caller_locations to get the current stack trace of the program.

From the docs for Kernel#caller_locations:

caller_locations(start=1, length=nil) → array or nil

source caller_locations(range) → array or nil

Returns the current execution stack—an array containing backtrace location objects.

See Thread::Backtrace::Location for more information.

So as we can see from the documentation, this method returns an array of Thread::Backtrace::Location objects, which you can use to determine whether or not to call binding.pry. Thread::Backtrace::Location provides a method called base_label, which returns the name of the method in the current position in the stack. You can use this to check whether the current stack trace runs through a method with a specific name.

Usage example:

def a
  caller_locations(0)
end
def b
  a
end
def c
  b
end

c.map(&:base_label)
#=> ["a", "b", "c", "<main>"]

So in your case, you'd use it like this:

binding.pry if caller_locations.map(&:base_label).include? function_name

If you're using an older version of Ruby (< 2.0), Kernel#caller_locations isn't available, and you'll have to use Kernel#caller instead:

caller(start=1, length=nil) → array or nil

caller(range) → array or nil

Returns the current execution stack—an array containing strings in the form file:line or file:line: in `method'.

The optional start parameter determines the number of initial stack entries to omit from the top of the stack.

A second optional length parameter can be used to limit how many entries are returned from the stack.

Returns nil if start is greater than the size of current execution stack.

Optionally you can pass a range, which will return an array containing the entries within the specified range.

def a(skip)
  caller(skip)
end
def b(skip)
  a(skip)
end
def c(skip)
  b(skip)
end
c(0)   #=> ["prog:2:in `a'", "prog:5:in `b'", "prog:8:in `c'", "prog:10:in `<main>'"]
c(1)   #=> ["prog:5:in `b'", "prog:8:in `c'", "prog:11:in `<main>'"]
c(2)   #=> ["prog:8:in `c'", "prog:12:in `<main>'"]
c(3)   #=> ["prog:13:in `<main>'"]
c(4)   #=> []
c(5)   #=> nil

You'll notice that the only difference between Kernel#caller_locations and Kernel#caller is that Kernel#caller returns an array of strings, not an array of Thread::Backtrace::Location objects. This means that you'll have to use something like regular expressions to match the method name, instead of just using Array#include? as we did with Kernel#caller_locations. In your case:

binding.pry if caller.any?{|stack_entry| stack_entry =~ /in `#{function_name}'$/}

For more information on getting stack traces in Ruby, see how to get a stack trace object in Ruby?

Community
  • 1
  • 1
Ajedi32
  • 45,670
  • 22
  • 127
  • 172
  • I ended up going with `binding.pry if caller.any?{|stack_entry| stack_entry =~ /[actual name of function]/}` because I could hardcode it (I'm just debugging). What is the purpose of the `$` symbol? I'm guess the 'extra' stuff is to allow for string interpolation inside of the regex? – AndrewLngdn Jan 13 '15 at 22:45
  • @AndrewLngdn Notice how all the strings returned by `caller` are in the form ```"file:line: in `method'"```? Well, ``/in `#{function_name}'$/`` matches all strings that end with ``in `method'"``, specifically. ([Example / Full Explanation](https://regex101.com/r/lG9cY2/1)) It's a bit more restrictive than just `/function_name/`, which matches the function name anywhere in the string, even in somewhere like the file name. Interpolation is done with `#{expression}`, the other characters are just part of the regular expression. – Ajedi32 Jan 14 '15 at 00:30
1

Try

binding.pry if caller.any?{|fn_name| fn_name.include?('function_name')}

Ref: How to get a stack trace object in Ruby?

Community
  • 1
  • 1
Prakash Murthy
  • 12,923
  • 3
  • 46
  • 74