CodeNewbie Community

loading...
Cover image for Pry debugger in Ruby

Pry debugger in Ruby

djuber profile image Daniel Uber ・11 min read

Ruby's standard library includes a perfectly usable interactive interpreter, irb, in addition to the non-interactive interpreter ruby, but I rarely choose to work with ruby without my preferred console environment, pry.

I'll try to say why I like it so much, show a few things you can do with pry that might surprise you, and why I find it indispensable in daily working with Ruby.

The Dark Days Before

When I started using Ruby, I used it as a replacement for shell scripting. I would edit a source code file, call ruby myfile.rb and let it run. There are a lot of programs you can write this way (and a generation of programmers that learned to write code this way), those are totally useful programs and just as powerful. What was missing for me was interactivity, and inspect-ability.

When I wanted to understand how something was working, or see why it wasn't, I would guess where things might be going wrong and add a lot of puts statements nearby to watch the flow and the values and compare them with my understanding, or cut the bottom half of the script out and just let it run right up to that point. This is a fight where you're pitting your wits against the computers complexity. It's a fight you'll win in the end, but you might take a beating along the way :(.

First Exposure

I was interviewing at a company (where I ended up going to work) and during the hands-on part, they opened a pry console and had me work there to make sure I understood how to use ruby. It didn't seem wildly different from irb, it provided a basic read-eval-print-loop, allowing me to type code, see the result, and type more code building on those definitions, and I didn't get a lot of exposure to it that would have led me to suspect there was something better just out of sight. I noted this new word "pry" as part of my inventory of the tools they were using, and when I got home I looked a little more into it.

I was impressed, and started to realize this was the piece of the language I had been missing, an immediate feedback live interaction environment, with tools to support you as you worked.

Ways I use Pry

Object inspection with ls

In IRB you can send the methods method to any object and get a list of all the names of functions it answers. This is great, when you have an array you can ask it "Array, what things can I ask or tell you?" and get a quick listing back

methods in IRB

In pry you can call the same [].methods code, exercise the same underlying functionality, get the same response from the language, but immediately start to see a few convenience layers happening to make you happier.

The answers are pretty printed, 1 item per line (short arrays that fit on a single line are still printed compactly), and if they're longer than your terminal screen is tall, instead of zipping to the end of the wall of text, a paginator (the unix less program in my case) will start and allow you to scroll up and down (and in less, I can do simple searches as well with the '/' key). If you hit the q key, it quits, the console goes back to the interpreter, and only the part of the answer that was visible is kept on the screen.

methods in pry

When I want to know what methods an object answers, I really find it useful to know which ones are special. I've seen hints where they suggest you subtract the base class Object's methods to get the parts that are unique to this object's class, rather than things that any object in ruby can answer

[].methods - Object.methods
=> [:to_h, :&, :*, :+, :-, :at, :fetch, :last, ...
Enter fullscreen mode Exit fullscreen mode

I also might have an idea of the methods I want to call, and just double check it's there, so I'll sort that response

([].methods - Object.methods).sort
=> [:&, :*, :+, :-, :<<, :[], :[]=, :all?, :any?, :append, :assoc, :at,...
Enter fullscreen mode Exit fullscreen mode

But since ruby is an object oriented language that uses class based inheritance, there's another way to filter this out, I can remove the object's classes superclasses methods and just see the ones that are defined at the most specific level. Rather than continue down that way, I'll show how I would do this in pry, which is the first killer feature (the pagination and pretty printing are just nice to have, ls is a must have).

I'll ask pry's ls command, which is meant to remind you of the Unix ls command, to show me the listing of methods of the object, and it will give me a listing in columns, sorted by the module or classes that define the code, from the bottom up (and automatically exclude the Object methods that I was filtering out before).

[3] pry(main)> ls []
Enumerable#methods: 
  chain           detect      each_with_index   find    grep_v    max_by     partition     slice_when

...
Array#methods: 
  &              clear        drop_while  insert        place                 rindex     sum       
  *              collect      each        inspect       pop                   rotate     take      
...
Enter fullscreen mode Exit fullscreen mode

What this tells me is that there are some methods like find and detect that are defined in the Enumerable module (which Array includes), and some like drop_while or collect that are defined in the Array class (and don't use inherited behavior directly). If you're working with a deep inheritance list, like an ActiveRecord model class in Rails, being able to see the things in the order they are defined, zooming in on the layers where a method is defined to see whether it's part of your app's logic, or part of the framework, is really handy.

Let's take a look at user defined code and see what it's like when you're not digging into the internals of Arrays or large classes like ActiveRecord models.

# when I start a class the prompt changes 
# from > to * letting me know that there's more needed 
[1] pry(main)> class Dog
[1] pry(main)*   attr_reader :age
[1] pry(main)*   
[1] pry(main)*   def initialize(age=1)
[1] pry(main)*     @age = age
[1] pry(main)*   end  
[1] pry(main)*   
[1] pry(main)*   def bark
[1] pry(main)*     puts "woof " * age
[1] pry(main)*   end  
[1] pry(main)* end  
=> :bark
# I can of course make an object and call its methods, old dogs seem to bark more
[2] pry(main)> Dog.new(10).bark
woof woof woof woof woof woof woof woof woof woof 
=> nil
# the default print representation, same as `puts Dog.new.inspect`
[3] pry(main)> Dog.new
=> #<Dog:0x0000559bb0532ae0 @age=1>
# pry's super charged inspection, via `ls`
[4] pry(main)> ls Dog.new
Dog#methods: age  bark
instance variables: @age
Enter fullscreen mode Exit fullscreen mode

The pry repl (and IRB) will print a kind of readable print representation of the Dog object.

Changing the default receiver with cd

If you're a unix command line user, you might type cd and ls a lot. Just like ls shows the listing of an object (including methods and variables), cd lets you enter into the object. This is super useful if you're working on code that's defined inside a method, and you want to be able to work interactively as though you were already in that method.

Reusing the sample class Dog, I'll step into an instance fido and begin working.

[11] pry(main)> fido = Dog.new(2)
=> #<Dog:0x0000559bb0669120 @age=2>

[12] pry(main)> cd fido

[13] pry(#<Dog>):1> whereami
Inside #<Dog>.

[14] pry(#<Dog>):1> ls
Dog#methods: age  bark
self.methods: __pry__
instance variables: @age
locals: _  __  _dir_  _ex_  _file_  _in_  _out_  pry_instance
Enter fullscreen mode Exit fullscreen mode

A few things are happening here. First, the prompt changed from pry(main) to pry(#), and a :1 shows after it. That's a nesting depth (you can think of this number as how many steps from the beginning you've gone, or how many times you have to exit before you're back at main again).

Once you're "inside" fido, any message you send is assumed to be a method call for fido (you now have the same effective namespace as code written inside the Dog class's definition). A simple example would be to get fido's age. From main, to access the reader, I would say fido.age and get back 2. If I've cd'd into fido, I can now say age and get the same answer. One important thing to bear in mind is that you don't have access to the old variables from "outside", only the names and information that the instance has, so you can't say fido anymore (that name has no meaning here). But you can call bark and ask for age, or even directly update the instance variable @age, without needing to give fido as the receiver.

Why is this invaluable? Because when code is written in the class file, it behaves the same way. This really helps write the exploratory code the way the files will be written (you're in the same context as the code you're writing) and speeds up any back and forth of copying and pasting between the console and the source files.

Multi-line Editing with edit

One last downside of programming from the repl is that ruby's syntax isn't really heavy on mandatory punctuation (you don't need to end a statement with a semicolon, like you would in other languages, you don't even need to put function call arguments in parentheses like Javascript, you don't have significant indentation like python) and there's not really a good way for the interpreter to know whether you were done typing or not. The fallback is any time you hit enter to get a new line, unless you're inside a delimited area like a block, a list, a method def, or a class definition, the interpreter evaluates that line and prints the result. This is normally the right thing for treating the console like a command prompt to ruby, but there's a perfectly valid style in ruby that puts method chains on multiple lines, like this:

[1,2,3,4,5]
  .map {|x| x * 2 }
  .select { |x| x.odd? }
  .none?
Enter fullscreen mode Exit fullscreen mode

The problem with pasting a code snippet like that into the repl is that the first line, with the array on it, is eagerly evaluated, answering the array [1,2,3,4,5] back to you, and the second line (which should have called map on that array) tries to call .map on main, which just raises an unfriendly error. If you pasted all of the lines at once from a file, you'll have one error message for each line after the first, since they're all raising similar errors (in this case, pry treats the leading dot as a call out to the shell, and responds with a message that there was a problem executing the system command: map {|x| x * 2 }, which wasn't what you expected or wanted.

This is where edit comes in, another hidden pry power.

I type edit and I'm prompted with the system editor (this might be vi, or nano, or something else on your system, and it's configurable via Pry.config.editor or environment variables).

I am able to edit any code (it's just a normal text editor, nothing is being evaluated) and then save and exit. When I do the temporary file that was created for the editor to change is loaded and evaluated. Only the result is shown (so for the pipeline above asking if none of "2 times the first five numbers" is odd, I get back true), but if I hit the up arrow, to retype the prior command, I see all four lines as a single statement.

The way I worked around this before I knew about edit was to move the dot up to the prior line (since a dot separates an object from a message, the interpreter waited for more input at the end of the line)


[1,2,3,4,5].
  map { |x| x * 2 }.
  select {|x| x.odd? }.
  none?
Enter fullscreen mode Exit fullscreen mode

Which worked, but it was a different style than the way my company's code was written in the file, required me to do some fixing up to use it, and some fixing up to copy out of the console and back into the files, and was error prone since once you hit enter and the interpreter swallowed the commands, you'd have to edit again.

There's one more way to do this, which is to give the name of a method you want to change, like this:

[65] pry(main)> edit Dog#bark
Enter fullscreen mode Exit fullscreen mode

The editor brings up just that section of the code, without the class definition surrounding it, and allows you to replace the in-memory behavior of Dogs any way you need.

Show method source

This is the one that saves me a ton of time. When I'm about to call a method on an object, I know ls will tell me the name of the method, but how do I know how many arguments to send it, and what it will do?

I could find the documentation, or start reading the code, but I'm in the environment, and ruby's preserved the source code locations of all the methods for me. Pry has a convenient show-method helper command that prints (or tries hard to) the location in the code that defines the method. Sometimes this won't work great, and you'll get a confusing answer: because of metaprogramming, it's possible that another function wrote the function that you're going to call, and those won't look exactly like you'd expect, but for most methods on most objects, especially the one's you're busy writing, this will be great. This usually also works fine for code you've typed into the console instead of a file

[56] pry(main)> show-method fido.bark

From: (pry):8:
Owner: Dog
Visibility: public
Signature: bark()
Number of lines: 3

def bark
  puts "woof " * age
end

Enter fullscreen mode Exit fullscreen mode

The From: (pry):8: says this was locally defined in the console, the rest of the header says this is a public method on the Dog class, that takes no arguments. Then it shows the code I typed before for bark.

It's handy that you can ask for the methods on any instance, or on the class by name (for instance methods of a class Ruby uses the # symbol, you'd ask show-method Dog#bark instead of Dog.bark, but other than that classes and their instances are interchangeable when looking for definitions of the behavior).

Oh, and it's a debugger

You can also use it as a source debugger, and that's probably it's normal mode of invocation.

Given a file like

#!/usr/bin/env ruby

def broken_method(a)
  a.of_course_this_fails
end

broken_method(20)
Enter fullscreen mode Exit fullscreen mode

If I wanted to see what's happening I could change it to the following:

#!/usr/bin/env ruby

require 'pry'

def broken_method(a)
  binding.pry # <-- this is new, and sets a breakpoint
  a.of_course_this_fails
end

broken_method(20)
Enter fullscreen mode Exit fullscreen mode

Now when I run that script, instead of an error message, I'm dropped down into a debugging session (inside the broken_method code, with the variable a defined as 20 already) and I can test the code I'm about to call. When you're done, exit and execution will continue. This is a way of re-injecting interactivity into your file based scripts so you can move faster. It's also more fun than adding puts statements to the code.

Check it out

To get this to try it, you'll want to install pry: gem install pry, or bundle add pry if you're in a project with a Gemfile. Any time you might open IRB to experiment with ruby, you can just type pry (or bundle exec pry) and have a supercharged interpreter running instead.

I barely did this project justice, and suggest reading their docs for more details and the dozens of features I don't personally use every day, that might make a huge difference in your workflow.

http://pry.github.io/ has the quickstart walkthrough and install instructions

https://github.com/pry/pry/wiki has a lot of links to external demonstrations and tutorials, too.

Discussion (0)

pic
Editor guide