3

I am using Pry under Ruby 2.5 to debug a problem within a base class (Net::HTTP)

I am getting an exception caused by an HTTP 404 response and I want to examine the body of the request that was made. To do this I want to inspect the block which was passed to start, but the start method does not have a parameter, it's called using yield:

Frame type: method

From: /usr/share/ruby/net/http.rb @ line 910 Net::HTTP#start:

    905: def start  # :yield: http
    906:   raise IOError, 'HTTP session already opened' if @started
    907:   if block_given?
    908:     begin
    909:       do_start
 => 910:       return yield(self)

Using Pry is there any way to view the source of a block, if that block is not passed in a &block parameter?

Saw Printing the source code of a Ruby block but it doesn't help me because I don't have a method parameter to use here.

Josh
  • 10,961
  • 11
  • 65
  • 108
  • As a hacky workaround, you could edit this file and change it to take the block as an explicit parameter (`&blk`) - that way you could call `.to_ruby` on it – max pleaner Jan 23 '20 at 18:00
  • Thanks @maxpleaner. I think I solved the `Net::HTTP` problem that triggered this question but I am still interested in the best way to inspect a block, ideally without having to edit gems or distro-provided ruby files. – Josh Jan 23 '20 at 18:02
  • 1
    Spoke too soon! Looks like I'll have to edit the source of the gems I'm using which make `Net::HTTP` calls... – Josh Jan 23 '20 at 18:07
  • 1
    If you're ever really stuck, don't forget you can patch out elements of `Net::HTTP` with your own methods that can intercept, inspect, report, and do other things with arguments and results. This can be very handy when debugging third-party libraries that are harder to navigate. – tadman Jan 23 '20 at 21:53

3 Answers3

2

TL;DR You can try to utilize Kernel#caller.


Let's consider a file called foo.rb:

(this example uses byebug, but the flow with binging.pry is almost the same)

1: require 'byebug'
2: 
3: def bar
4:   byebug
5: 
6:   yield
7: end
8: 
9: bar { 'some string' }

When we run this file with ruby foo.rb, we will be stopped at the byebug statement.

[1, 9] in foo.rb
   1: require 'byebug'
   2: 
   3: def bar
   4:   byebug
   5: 
=> 6:   yield
   7: end
   8: 
   9: bar { 'some string' }

Then we can execute caller and something like the following will be printed:

(output is intentionally reduced and formatted)

(byebug) caller
[
  "~/.rbenv/versions/2.7.0/lib/ruby/gems/2.7.0/gems/byebug-11.0.1/lib/byebug/processors/command_processor.rb:97:in `process_commands'",
  "~/.rbenv/versions/2.7.0/lib/ruby/gems/2.7.0/gems/byebug-11.0.1/lib/byebug/processors/command_processor.rb:55:in `at_line'",
  "~/.rbenv/versions/2.7.0/lib/ruby/gems/2.7.0/gems/byebug-11.0.1/lib/byebug/context.rb:98:in `at_line'",
  "foo.rb:6:in `bar'",
  "foo.rb:9:in `<main>'"
]

As you can see, caller returns the current execution stack as an array containing strings in the form file:line in `method'.

The last string in this array specifies where bar was called.

Knowing this information you can open the file with that bar call and track which block was passed into it.

(foo.rb:9 in this particular case)

Hope that helps.

As a bonus, there is an amazing article by Tenderlove - I am a puts debugger, where you can find a solution probably for every Ruby debugging problem.

Marian13
  • 7,740
  • 2
  • 47
  • 51
2

So, the real issue here is that I didn't read all the Pry help (specifically rescue-pry) and didn't know about the up command...

Usage: up [OPTIONS]
  Go up to the caller's context. Accepts optional numeric parameter for how many frames to move up.
  Also accepts a string (regex) instead of numeric; for jumping to nearest parent method frame which matches the regex.
  e.g: up      #=> Move up 1 stack frame.
  e.g: up 3    #=> Move up 2 stack frames.
  e.g: up meth #=> Jump to nearest parent stack frame whose method matches /meth/ regex, i.e `my_method`.

    -h, --help      Show this message.

By executing up within a pry session from a raised exception, I was able to get to the block where the exception was raised and inspect the local variables I needed.

Josh
  • 10,961
  • 11
  • 65
  • 108
2

Using Pry is there any way to view the source of a block, if that block is not passed in a &block parameter?

Your problem is twofold: you have to 1) get the implicit block and 2) print its source.

For 1) you can simply call Proc.new within the method's context to get its block argument as a proc.

So instead of:

def foo(&block)
  # ...
end

You could also retrieve the block via:

def foo
  block = Proc.new
  # ...
end

For 2) you could use the sourcify gem:

require 'sourcify'

prc = Proc.new { |i| i * 2 }
prc.to_source
#=> "proc { |i| (i * 2) }"

Applied to your problem:

# foo.rb
require 'pry'
require 'sourcify'

def foo
  binding.pry
end

foo { |i| i * 2 }
$ ruby foo.rb

From: foo.rb @ line 6 Object#foo:

    4: def foo
 => 5:   binding.pry
    6: end

[1] pry(main)> block = Proc.new
#=> #<Proc:0x00007f9373b1c368@foo.rb:9>

[2] pry(main)> block.call(123)
#=> 246

[3] pry(main)> block.to_source
#=> "proc { |i| (i * 2) }"
Stefan
  • 109,145
  • 14
  • 143
  • 218