Pry Tips & Tricks
I joined the Wombat team a few months ago, and have been working on the ThreatSim team. We had a bit of a bug backlog, so I’ve been doing lots of bug fixing.
ThreatSim is a Ruby on Rails application; any developer out there who works with Rails has probably used Pry extensively in debugging their application. Pry “pauses” your application’s execution and lets you observe and manipulate state, wherever the pry happens to be.
Most pry usage is pretty simple - put a pry in your code, cause that line of code to be executed, and then poke around in the session in your terminal.
For me, this can feel unwieldy when I am trying to do a broad examination of the application. Pry is great at showing me the state of the variables contained within the method that the Pry was placed at, but I don’t always want to see just this code and its variables, I want to skip around the application and peek into different components.
The ThreatSim codebase is large, and still very new to me, so I’ve had to sharpen my skills with tools like Pry to most quickly wrap my head around complex applications.
All of the following tips are intended to be used inside the Pry REPL.
Looking at methods
You can use show-method
to view any piece of code in your application.
There are two primary ways I use show-method
:
show-method
without a specific method argumentshow-method
with a specific method argument
‘show-method’ without method arguments
If you call just show-method
it will show all of the code in the method that you’ve placed the pry.
For example:
<CookiesController:0x00007f9156c57670>:0> show-method -l
From: /full/path/to/file.rb @ line 78:
Owner: CookiesController
Visibility: private
Number of lines: 37
78: def load_cookie_jar
79: cookies[:tasty] = true
80: count = count_cookies
81: .
.
lots of other code here
.
.
111: require "pry"; binding.pry
112:
113: Repo::FakeClass::NotActuallyAModule.do_something(options)
114: end
(An astute observer may notice that I passed the -l
argument to show-method
. This will print out the line numbers along side the method itself.)
‘show-method’ with method argument
See that the line about to be executed in the above example (line 113)? What if you want to see what that method is, without jumping into your code editor?
If you want to see what that method is, it’s easy! Use show-method App::Class.method_name
:
<CookiesController:0x00007f9156c57670>:0> show-method Repo::FakeClass::NotActuallyAModule.do_something -l
From: full/path/to/file.rvb @ line 165:
Owner: #<Class:Repo::FakeClass::NotActuallyAModule>
Visibility: public
Number of lines: 22
165: def do_something(options = {})
166: cookies_type = options.fetch(:cookie_type)
167: is_tasty? = options.fetch(:is_valid, false)
168:
169: if is_tasty?
170: log_it "load_cookies", "is_tasty"
171: options[:consumed] = nil
172: options[:pairs_with] = ""
.
.
.
185: end
186: end
In the above example, I passed in the full “namespace path” (I just made that term up) to reveal the code behind any method you’d like to see.
(I’m still adding -l
to force line-numbers to be printed out.
more about show-source
from Pry
Breakpoints in Pry
Often, when using show-method
to look at other pieces of code about to be executed, you might decide you want to examine that specific method with a binding.pry
in it.
For example, in the last section we just looked at Repo::FakeClass::NotActuallyAModule.do_something
using show-method
, but what if we wanted to step through the method, one line at a time, in pry?
Enter breakpoints. Just like with javascript in the browser, you can add/remove breakpoints to your code with Pry. You don’t have to exit the session, jump to the new method, and add a binding.pry
to it.
You’ll need to add pry-byebug to your Gemfile.
With pry-byebug, breakpoint functionality is fairly straightforward:
break
shows all current breakpoints. (this list should be empty if you’re runningbreak
for the first time.break <Class#method>
adds a breakpoint to the start of the given method.
if you add a breakpoint, and call break
you’ll see something like:
# Enabled At
-------------
1 Yes Cookies::CookiesController::CookiesLoader.do_something
Once you’ve added a breakpoint, you can continue
your way from the current pry and code execution will stop when it hits that breakpoint.
break --help
is a fruitful summary of what breakpoint-related methods are available to you.
Where was I?
Sometimes, I go so far down a rabbit hole of digging around in Pry, I forget where the binding.pry
actually is, and what I was trying to do in the first place.
Enter whereami
This command simply prints out the code surrounding the current binding.pry
. It’s run by default as soon as you hit the pry, which is how you can quickly get your bearings.
View stack traces
What was that stack trace from the last exception you saw?
wtf
prints said stack trace:
<CookiesController:0x00007f9156c57670>:0> not_a_variable
NameError: undefined local variable or method `not_a_variable' for #<CookiesController:0x00007f9156c57670>
from (pry):16:in `load_cookie_jar'
and then, anytime later, call wtf
in pry:
<CookiesController:0x00007f9156c57670>:0> wtf
Exception: NameError: undefined local variable or method `not_a_variable' for #<CookiesController:0x00007f9156c57670>
--
0: (pry):16:in `load_live_action'
1: /Users/joshthompson/.rvm/gems/ruby-2.3.7/gems/pry-0.10.3/lib/pry/pry_instance.rb:355:in `eval'
2: /Users/joshthompson/.rvm/gems/ruby-2.3.7/gems/pry-0.10.3/lib/pry/pry_instance.rb:355:in `evaluate_ruby'
3: /Users/joshthompson/.rvm/gems/ruby-2.3.7/gems/pry-0.10.3/lib/pry/pry_instance.rb:323:in `handle_line'
4: /Users/joshthompson/.rvm/gems/ruby-2.3.7/gems/pry-0.10.3/lib/pry/pry_instance.rb:243:in `block (2 levels) in eval'
Calling all callers
Ever wanted to see what called the code that hit your breakpoint?
I sure have!
As usual, Stack Overflow has a most helpful answer.
You can just call caller
in pry, to get a full list of all current callers.
The author of the post points out that you immediately get a giant array of mostly-irrelevant items, and suggests filtering by keyword, using a one-liner like so:
caller.select {|line| line.include? "current_repo_name" }
Or, alternatively:
caller.reject { |l| l[".rvm/gems"] }
(I’m partial to the bottom one)
Pry is an incredible tool, and I am continually impressed at how powerful it is. I am thankful to the team that maintains it!