Debugging Ruby Code with Pry

Your comprehensive guide to Pry and how to use it .

Image for post
Image for post
Debugging: the process of identifying and removing errors from code

Every programmer has faced a situation where they go to execute their code and get an error message they’re confused by, or even worse, the code runs successfully but nothing is output. This was me, and before I learned about Pry I would do things like combing through my code, line by line, trying to guess where the error could be. I even had a phase of inserting puts statements (puts “The error is here!”) hoping to figure out where the error was originating from. Once I learned about Pry, I never looked back. I truly believe it is one of the most important tools for beginning Ruby programmers.

What is Pry?

Pry is a REPL (Read, Evaluate, Print, Loop) that pauses your code wherever inserted and allows you to test/debug. When your code gets to the ‘pry’ it will freeze and your terminal will turn into a REPL right in the middle of your program.

How do I use Pry?

The first step is to make sure you have the pry gem installed by typing the following into your terminal:

gem install pry

Once it’s installed, you will have to require it at the top of any file you plan on using it in like so:

# test.rb
require 'pry'

Now it's time to actually install a pry break into your code using the following snippet:
binding.pry

Let’s create a file called ‘practice.rb’ and insert the following code into it:

require 'pry'

def my_fancy_method
inside_method = "We are now inside the method."
puts inside_method
pry_coming = "We are about to see how pry works!"
binding.pry frozen = "Pry froze the program before it got to this point!"
puts frozen
end

my_fancy_method

Now, when you call “my_fancy_method” before the code finishes fully executing, it will run into the “binding.pry” and kick out into the REPL before it gets to the end. Inside the REPL, you will have everything that was defined before binding.pry, but not anything afterwards. Your terminal should look something like this:

3: def my_fancy_method
4: inside_method = "We are now inside the method."
5
: puts inside_method
6: pry_coming = "We are about to see how pry works!"
=> 7: binding.pry8: frozen = "Pry froze the program before it got to this point!"
9
: puts frozen
10: end
[1] pry(main)>

At the bottoms you will see the pry(main)> prompt indicating you are currently inside the pry REPL. Let’s test it out. Since the variable inside_method was defined before binding.pry, it should be known to Pry.

[1] pry(main)> inside_method=> "We are now inside the method."[2] pry(main)>

It worked!! Now, lets try calling the variable frozen which isn’t defined until after the binding.pry on line 8:

[[1] pry(main)> inside_method=> "We are now inside the method."[2] pry(main)> frozenNameError: undefined local variable or method `frozen' for main:Object

It doesn’t know what frozen is, because as far as its concerned it hasn’t gotten to that definition yet so it does not exist. Make sense?

[3] pry(main)> exitPry froze the program before it got to this point!

When you type ‘exit’ to leave Pry, it will continue executing the rest of the code, as seen here.

Using Pry to Debug

Ok. Now that we understand how to install, and use pry, we’ll see how it helps us debug code. Here are the most common uses of Pry for me so far in my early journey through the coding world.

Use Case #1: Checking Variables

One common mistake for early coders is improperly assigning values to variables. For example, let's say you had the following code:

require 'pry'

def simple_cubing_tool(number)
number * number * number
puts "The answer is #{number}!"
end

simple_cubing_tool(4)

It takes in an integer as an argument and multiplies it times itself 3 times. Then it returns a statement giving you your number cubed. Now in a simple case like this you may see the problem already, but let’s take a look at when we run our method with the input of 4.

// ♥ > ruby practice.rb

The answer is 4!

4?? But 4 cubed is 64 not 4. Lets see what happened by using Pry:

require 'pry'

def simple_cubing_tool(number)
number * number * number

binding.pry
puts "The answer is #{number}!"
end

simple_cubing_tool(4)
3: def simple_cubing_tool(number)
4: number * number * number
=> 5: binding.pry
6: puts "The answer is #{number}!"
7
: end
[1] pry(main)> number
=> 4

If we type number into pry, even after line four: (number * number * number) has been executed, we see the value of number is still 4. Oops! we forgot to assign number*number*number value to a variable. How do we fix this?

require 'pry'

def simple_cubing_tool(number)
num_cubed = number * number * number
puts "The answer is #{num_cubed}!"
end

Ok now lets run it again.

// ♥ > ruby practice.rb

The answer is 64!

Perfect! Now you see how to use Pry to check the value of variables at different stages of your program.

Use Case #2: Seeing where you are in a nested Hash/Array

Let’s say we have an array of hashes (AOH) defined as follows:

nested = [
{:fruit => {
:apple => 1,
:banana => 2,
:grape => 6
},
:pets => {
:fido => "dog",
:whiskers => "cat",
:charles => "mouse",
:bitey => "snake"
},
:teams => {
:new_york => {
:baseball => ["mets", "yankees"],
:basketball =>["knicks", "nets"],
:football => ["giants", "jets"],
:hockey => ["rangers", "islanders"]
},
:los_angeles => {
:baseball => ["dodgers", "angels"],
:basketball =>["lakers", "clippers"],
:football => ["rams", "chargers"],
:hockey => ["kings"]
},
:chicago => {
:baseball => ["cubs"],
:basketball => ["bulls"],
:football => ["bears"],
:hockey => ["blackhawks"]
}
}
}
]

Our goal here is to iterate through this hash and return all of the basketball teams for each city in the form of:

“The basketball teams for (city name) are (basketball team names).”

Now, this may seem complicated, but using our friend Pry, we should be able to take this step by step.

def list_basketball_teams_by_city
nested.each do |element|
element.each do |outer_key, outer_value|
binding.pry
end
end
end

list_basketball_teams_by_city
// ♥ > ruby practice.rbTraceback (most recent call last):
5: from practice.rb:45:in `<main>'
4: from practice.rb:37:in `list_basketball_teams_by_city'
3: from practice.rb:37:in `each'
2: from practice.rb:38:in `block in list_basketball_teams_by_city'
1: from practice.rb:38:in `each'
practice.rb:39:in `block (2 levels) in list_basketball_teams_by_city': undefined method `pry' for #<Binding:0x00007faadd86ecc0> (NoMethodError)

When we run this code we get a NoMethodError — undefined method ‘pry’… hmm..

Oops! we forgot to require ‘pry’ at the top of our code!!!! (I actually made this mistake while writing this so figured I would keep it in)

require 'pry'def list_basketball_teams_by_city
nested.each do |element|
element.each do |outer_key, outer_value|
binding.pry
end
end
end

list_basketball_teams_by_city
// ♥ > ruby practice.rb
From: /Users/sean.laflam/Development/code/mod1/practice.rb:41 Object#list_basketball_teams_by_city:
38: def list_basketball_teams_by_city(array)
39: array.each do |element|
40: element.each do |outer_key, outer_value|
=> 41: binding.pry
42: end
43: end
44: end
[1] pry(main)>

Much better!! Now based on our code, we can see we are two levels into our nested hash array, and on the first loop of each. We can confirm this by plugging outer_key and outer_value into our pry terminal:

[1] pry(main)> outer_key=> :fruit[2] pry(main)> outer_value=> {:apple=>1, :banana=>2, :grape=>6}

Good, but we still need some work. We won’t get to the :teams hash until the 3rd loop of our inner each loop and then we still need to dive in 2 levels deeper to return the array of baseball team names. Let’s keep going and add some logic. (Remember: use “exit!” to back all the way out of PRY)

def list_basketball_teams_by_city(array)
array.each do |element|
element.each do |outer_key, outer_value|
if outer_key == :teams
outer_value.each do |city, sports_hash|
binding.pry
end
end
end
end
end
38: def list_basketball_teams_by_city(array)
39: array.each do |element|
40: element.each do |outer_key, outer_value|
41: if outer_key == :teams
42: outer_value.each do |city, sports_hash|
=> 43: binding.pry
44: end
45: end
46: end
47: end
48: end

We should now be inside of the :teams hash and on the first iteration of our cities hash within :teams which would be :new_york.

We can check this again with pry by entering “city” and “sports_hash” into PRY.

[1] pry(main)> sports_hash=> {:baseball=>["mets", "yankees"],:basketball=>["knicks", "nets"],:football=>["giants", "jets"],:hockey=>["rangers", "islanders"]}[2] pry(main)> city=> :new_york

Looks good! We’re getting close to the desired output! Lets bring it home.

require 'pry'def list_basketball_teams_by_city(array)
array.each do |element|
element.each do |outer_key, outer_value|
if outer_key == :teams
outer_value.each do |city, sports_hash|
sports_hash.each do |sport, team_name_array|
if sport == :baseball
puts "The basketball teams for #{city} are #{sport}."
end
end
end
end
end
end
end
list_basketball_teams_by_city(nested)// ♥ > ruby practice.rbThe basketball teams for New York are baseball.The basketball teams for Los Angeles are baseball.The basketball teams for Chicago are baseball.

Oh no!! So close but something is wrong. Lets use pry to figure out what happened.

if sport == :baseball
binding.pry
puts "The basketball teams for #{city} are #{sport}."
end
38: def list_basketball_teams_by_city(array)
39: array.each do |element|
40: element.each do |outer_key, outer_value|
41: if outer_key == :teams
42: outer_value.each do |city, sports_hash|
43: sports_hash.each do |sport, team_name_array|
44: if sport == :baseball
=> 45: binding.pry
46: puts "The basketball teams for #{city} are #{sport
47
: end
48: end
49: end
50: end
51: end
52: end
53: end
[1] pry(main)> sport=> :baseball[2] pry(main)> team_name_array=> ["mets", "yankees"][3] pry(main)>

There are a couple of errors here. If we check the sport it returns :baseball, but we’re looking for basketball teams for each city. The error is on line 44 in our if statement, where we say if sport ==:baseball instead of :basketball.

Then, we returned the wrong variable in our puts statement. We want to return the array of basketball team names for that city, which is defined as “team_name_array” yet we entered puts The basketball teams for #{city} are #{sport}.

Here is the final debugged code with these corrections added:

require 'pry'def list_basketball_teams_by_city(array)
array.each do |element|
element.each do |outer_key, outer_value|
if outer_key == :teams
outer_value.each do |city, sports_hash|
sports_hash.each do |sport, team_name_array|
if sport == :basketball
puts "The basketball teams for #{city} are #{team_name_array}."
end
end
end
end
end
end
end
list_basketball_teams_by_city(nested)
// ♥ > ruby practice.rb
The basketball teams for New York are ["knicks", "nets"].The basketball teams for Los Angeles are ["lakers", "clippers"].The basketball teams for Chicago are ["bulls"].~/.../code/mod1 // ♥ >

You can work on this code even further to make the output “prettier” but as you can see, Pry was extremely helpful in helping us get to the correct output.

Use Case #3: Anywhere else you need to debug your Code!!!

You can use Pry in any application that you seem fit where you need to debug errors in your code. Now that you’re a Pry wizard get out there and test it on your own!

SWE

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store