Pry-ing open Ruby


Pry is an excellent Ruby REPL and debugger and is my go-to tool for both purposes. In this article, I'll go through how to get Pry going in a Ruby project and hit the highlight reel of its most useful methods and features.

Installing Pry for REPL and Debugging

Here are the gems we need. The pry gem supplies the REPL and the pry-byebug plug-in adds required debugging commands. To see documentation on code in the REPL, you'll need pry-doc. If you want nice REPL text highlighting, use pry-coolline.

Below are two methods for installing Ruby Gems. For Gems I use globally such as Pry, I generally install from the command line, but use whatever method you prefer. If you only want the REPL, skip pry-byebug.

Installing pry from the command-line

lee@lees-rig:~$gem install pry pry-doc pry-coolline pry-debug

Installing pry from a Gemfile.

Add the following lines of code to your Gemfile. These are the most current versions as of the date of this article, so if you need to get the most updated version of the gems go to pry, pry-byebug, pry-coolline, and pry-doc.

gem 'pry', '~> 0.11.3'         # the pry repl iself (required)
gem 'pry-coolline', '~> 0.2.5' # add text highlighting. (neat but optional)
gem 'pry-doc', '~> 0.13.4'     # supplies documentation in pry (recommended)
gem 'pry-byebug', '~> 3.4'     # adds debugging commands (needed for debugging)

Pry as a REPL

I use REPLs to test code snippets while I work, and Pry is the best REPL for Ruby (in my opinion). IRB is a useable REPL, but as simple as it is to install pry, why not use it? Let's fire up Pry from the command line. Open your terminal of choice, type pry and press enter.


REPL commands

Pry has a myriad of useful commands, but here is a short list of the ones I find most useful:

_underscore returns the last output. This is quite useful if you want to augment or extend your previous return value.

[1] pry(main)> hi = "hi"
=> "hi"
[2] pry(main)> hi_ally = _ + " Ally"
=> "hi Ally"

_out_ — the array of all outputs.

[3] pry(main)> _out_.each { |line| puts line }
hi Ally

_in_ — the array of all inputs.

[4] pry(main)> _in_[0]
=> "hi = \"hi\"\n"

⇅ up/down arrow — cycles through the array of session inputs. A single press of the up arrow gives your last entry. This is quite useful for typos.

tab — auto-completes your entry if possible. If there is more than one possibility, pressing tab twice lists the all the options.

[4] pry(main)> hi
hi       hi_ally  hist     history

ls — lists the variables and methods within the current scope.

[18] pry(main)> ls hi_ally
Comparable#methods: <  <=  >  >=  between?  clamp
  %            casecmp         delete_suffix!         gsub      ord           slice        to_s
  ... all the string methods
  capitalize!  delete_suffix   grapheme_clusters      oct       size          to_r
self.methods: __pry__

? <topic of interest> — returns the documentation. To look up methods by class, use #. If you have an instance of the class, use it instead. ? Array#each gives the same result as ? [1,2,3].each.

[17] pry(main)> ? String#upcase

From: string.c (C Method):
Owner: String
Visibility: public
Signature: upcase(*arg1)
Number of lines: 6

Returns a copy of str with all lowercase letters replaced with their
uppercase counterparts.

See String#downcase for meaning of options and use with different encodings.

   "hEllO".upcase   #=> "HELLO"

$ <topic of interest> — returns the code for the topic. Works identically to ?.

cd <name of object>cd into an object to make it the working context.

cd ..exits the object or context. Cd works much as it does in the terminal.

[10] pry(main)> cd hi_ally
[11] pry("hi Ally"):1> ls
Comparable#methods: <  <=  >  >=  between?  clamp
  %            casecmp         delete_suffix!         gsub      ord           slice        to_s
self.methods: __pry__
locals: _  __  _dir_  _ex_  _file_  _in_  _out_  _pry_
[12] pry("hi Ally"):1> cd ..
[13] pry(main)>

exit — does the same thing as cd .. except that it will exit Pry in main.

.<shell command> — executes a shell command instead of a Pry command.

[12] pry(main)> .ls
bin  Gemfile  Gemfile.lock

crtl+r — pressing ctrl+r opens a search of the input history. Pressing ctrl+r again while in the search toggles through the possible matches.

(reverse-i-search)`hi_ally': ally = _ + " Ally"

Pry as a Debugger

Ok, so here is the really handy stuff. Cracking into your code while it's running is a rapid way to solve tricky issues. Pry freezes the code at specific points and lets you query variables and more via its REPL.

Debugger commands

Here are the most useful debugging commands:

step — executes the current line, and stops at the first possible occasion. Will step inside of called methods. Also, stops at all binding.pry. Note: will step inside of binding.pry itself if it is the next line of code.

next — continues execution until the next line in the current function is reached, or it returns. Does not step inside called methods. Also, stops at all binding.pry.

continue — runs code until the next binding.pry

!!! — leaves the debugger session. Very useful if you get stuck long loop.

Okay, Let's do a little debugging. Here's some sad, broken code to work with. Copy the code into a file called broken.rb to follow along.

def shuffle_if(arr)
  if arr.length > 10
    arr.shuffle! # shuffles in place

arr = (1..10).to_a # creates a basic array
arr = shuffle_if(arr) # re-assigns arr to it's shuffled self?
puts arr.last

running broken.rb we get the following

lee@lees-rig:~$ruby broken.rb
Traceback (most recent call last):
broken.rb:9:in `<main>': undefined method `last' for nil:NilClass (NoMethodError)

So you and I both know what's wrong here, but let's assume we didn't. From the error, we can glean that the value of arr at the time we call arr.last is no longer an array and that arr == nil, but let's drop in a binding.pry statement to set a breakpoint. Also, let's require pry so we can use it.

require 'pry'

def shuffle_if(arr)
  if arr.length > 10
    arr.shuffle! # shuffles in place

arr = (1..10).to_a # creates a basic array
arr = shuffle_if(arr) # re-assigns arr to it's shuffled self?
puts arr.last

Rerun the file using ruby broken.rb. This time it opens the Pry debugger. Let's return arr so we can take a look at its value.

$ ruby broken_2.rb

From: /home/broken.rb @ line 13 :

     8:   end
     9: end
    11: arr = (1..10).to_a # creates a basic array
    12: binding.pry
 => 13: arr = shuffle_if(arr) # re-assigns array to it's shuffled self?
    14: puts arr.last

[1] pry(main)> arr
=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

At our current position at the binding.pry, I can't indentify any issues with arr. Let's step forward.

[3] pry(main)> step

From: /home/broken.rb @ line 4 Object#shuffle_if:

    3: def shuffle_if(arr)
 => 4:   if arr.length > 10
    5:     arr.shuffle! # shuffles in place
    6:   end
    9: end

[3] pry(main)> arr
=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[4] pry(main)> arr.length
=> 10

At this point in the code, we can see what's happening. Arr is still fine but given its length of 10 the if statement won't execute. Since Ruby methods return nil if not given a return value, we'll be assigning arr a value of nil. Let's step one more time to make sure.

[1] pry(main)> step

From: /home/broken.rb @ line 12 :

     7: end
     9: arr = (1..10).to_a # creates a basic array
    10: binding.pry
    11: arr = shuffle_if(arr)  # reassigns array to it's shuffled self?
 => 12: puts arr.last

[1] pry(main)> arr
=> nil

As expected, the value of arr is nil. Let's leave the debugger using the !!! command.

[1] pry(main)> !!!

and fix our code.

# removed the require "pry" as it is no longer needed

def shuffle_if(arr)
  if arr.length >= 10 # makes sure our array is shuffled
    arr.shuffle! # shuffles in place

arr = (1..10).to_a # creates a basic array
# removed the binding.pry don't forget and leave them behind!
shuffle_if(arr) # removed reassignment, if shuffling does not occur variable is not assigned a nil value
puts arr.last # now logs a random number between 1 and 10.

That was a quick intro to Pry and debugging Ruby applications. Pry is a great tool and can get you through some weird spots in your code. Recently, the $ method helped me to locate an unexpected name collision. In a future article, we'll address incorporating pry as the rails console and using it to debug Rails applications in development.

further resources

Here are some additional resources for getting the most out of Pry.