Advanced Debugging with Pry

Published Feb 01, 2017
Advanced Debugging with Pry

This tutorial helps broaden your basic knowledge of debugging by providing you with a different approach and tool for debugging in Ruby.
Before we begin I will like us to establish some common ground. A basic understanding of the following is required:

Introduction

Pry was and is still being developed by John Mair (project founder; aka banisterfiend; twitter; blog), Conrad Irwin and Ryan Fitzgerald. It is also aided by a healthy number of contributors.

Simply put, Pry is IRB on steroids. With Pry, you get all the features of IRB, plus some more. Besides being able to intercept sessions and perfom REPL-like actions in your terminal, you are also able to use your command shell integrations.

Pry can do quite a number of things; however, for the sake of this tutorial, we will focus on using pry to debug.

To set up pry on your machine, run the gem install pry command. This would fetch pry from rubygems.org

Basic Usage

As mentioned above, you can use pry to intercept your session and evaluate each method call during the interception ,

Here we have a class, Greet, that greets people in several languages as shown below:

class Greet
  def self.english(username)
    "Hello #{username}"
  end

  def self.spanish(username)
    "Holla #{username}"
  end

  def self.deutch(username)
    "Hallo #{usernme}"
  end

  def self.aragonese(username)
    "ola #{username}"
  end
end

You probably noticed that we have a typo in the Deutch method.

If we try to get Deutch greeting by calling the method using Greet.deutch("John Doe"), you'll get an undefined local variable or method 'usernme' error.

If we put pry here, we would be able to evaluate each method individually.

In order to use pry, we must put it into the file we're working with and "bind" it at the specific point we want to intercept.

In this case, we will have

require 'pry';

class Greet
  def self.english(username)
    "Hello #{username}"
  end

  def self.spanish(username)
    "Holla #{username}"
  end

  def self.deutch(username)
    binding.pry
    "Hallo #{usernme}"
  end

  def self.aragonese(username)
    "ola #{username}"
  end
end

When we run the file again using the Deutch Greeting, Greet.deutch("John Doe"), a pry session is initiated. We would be able to access all the methods and arguments in the class.

From: /Users/Ruby/tutorial.rb @ line 15 Greet.deutch:

   14: def self.deutch(username)
=> 15:  binding.pry 
   16:  "Hallo #{usernme}"	
   17: end    
  
# Checking the argument being passed to the method
[1] pry(Greet)> username
=> "John Doe"

# Making method calls in pry
[2] pry(Greet)> aragonese(name)
=> ola John Doe

# Running individual blocks of code
# In an attempt to debug our error we will run the content of your `deutch` method
[3] pry(Greet)> "Hallo #{usernme}"
NameError: undefined local variable or method `usernme' for Greet:Class

If we look closely we'll find that we have argument name and the one in our code block dont match

We will simply fix this error by adding the missing character in our code block 

[4] pry(Greet)> "Hallo #{username}"
=> Hallo John Doe

The application of this are not limited to method calls or accessing variables alone. To exit your pry session, you can enter the command exit or ctrl + D.

Doing More with Pry

What if we could inspect all the local variables in our code snippet within our pry session?

From: /Users/Ruby/tutorial.rb @ line 15 Greet.deutch:

    14: def self.deutch(name)
 => 15:   binding.pry
    16: end    
    
 # lets see the methods and local variables available
 # We do this by running the 'ls' command
 
[1] pry(Greet)> ls
Greet.methods: aragonese  deutch  english  spanish
locals: _  __  _dir_  _ex_  _file_  _in_  _out_  _pry_  name

## Then we run the 'cd' command against our name variable
[2] pry(Greet)> cd name

And we run the 'ls' command to see all the methods for our name variable whose data type is a string 


[3] pry("John Doe"):1> ls
Comparable#methods: <  <=  >  >=  between?
String#methods:
  %    []           capitalize!  chr         downcase        encode          gsub      intern   next!      rindex      setbyte      squeeze!     sum        to_str              unicode_normalized?
  *    []=          casecmp      clear       downcase!       encode!         gsub!     length   oct        rjust       shellescape  start_with?  swapcase   to_sym              unpack
  +    ascii_only?  center       codepoints  dump            encoding        hash      lines    ord        rpartition  shellsplit   strip        swapcase!  tr                  upcase
  <<   b            chars        concat      each_byte       end_with?       hex       ljust    partition  rstrip      size         strip!       to_c       tr!                 upcase!
  <=>  bytes        chomp        count       each_char       eql?            include?  lstrip   prepend    rstrip!     slice        sub          to_f       tr_s                upto
  ==   bytesize     chomp!       crypt       each_codepoint  force_encoding  index     lstrip!  replace    scan        slice!       sub!         to_i       tr_s!               valid_encoding?
  ===  byteslice    chop         delete      each_line       freeze          insert    match    reverse    scrub       split        succ         to_r       unicode_normalize
  =~   capitalize   chop!        delete!     empty?          getbyte         inspect   next     reverse!   scrub!      squeeze      succ!        to_s       unicode_normalize!
self.methods: __pry__
locals: _  __  _dir_  _ex_  _file_  _in_  _out_  _pry_

It's a lot right? With the above, you do not neccesarily have to know all of Ruby's string methods by heart. However, I recommend you to look into each one of the methofs to know when and when not to use them. Ruby Doc is very useful in this regard. Kudos to you if you already know all these methods by heart! I honestly wouldnt mind buying you coffee if you're in the neighbourhood!

You can use the approach above to look into the internals of both your File and Exception classes.

To do that, we must modify our pry session for this hedgecase:


From: /Users/Ruby/tutorial.rb @ line 15 Greet.deutch:

    14: def self.deutch(name)
 => 15:   binding.pry
    16: end    
    
# We will trigger an exception by attempting to divide a string by an integer
 
[1] pry(Greet)> name/2
NoMethodError: undefined method `/' for "John Doe":String
from (pry):1:in `deutch'

# This exception is stored in the _ex_ local variable we saw above. 
# We can access the exception message at any point in time by looking into the variable

[2] pry(Greet)> _ex_.message
=> "undefined method `/' for \"John\Doe":String"

# Then we can run a backtrace also using the  _ex_ variable

[3] pry(Greet)>_ex_.backtrace
["(pry):1:in `deutch'",
 "/Users/Admin/.rbenv/versions/2.2.3/lib/ruby/gems/2.2.0/gems/pry-0.10.4/lib/pry/pry_instance.rb:355:in `eval'",
 "/Users/Admin/.rbenv/versions/2.2.3/lib/ruby/gems/2.2.0/gems/pry-0.10.4/lib/pry/pry_instance.rb:355:in `evaluate_ruby'",
 "/Users/Admin/.rbenv/versions/2.2.3/lib/ruby/gems/2.2.0/gems/pry-0.10.4/lib/pry/pry_instance.rb:323:in `handle_line'",
 ....

The above is extremely useful when you're trying to debug complex projects and scripts. You can also check last exception message by using the wtf command in pry. The backtrace will help seek out the origination of your bug.

Using pry in Loops

Imagine a simple method that takes an array argument and outputs a hash with the names of students and an assigned serial number to each students.

Such a method will look like the one we have below:

def student_serial(i)
  new_hash = {}
  i.each_with_index do |value, index|
    new_hash["#{value}"] = index
  end 
  new_hash
end 

Using the conditional, you can specify at what point you want to intercept your code and initiate a pry session.

def student_serial(i)
  new_hash = {}
  i.each_with_index do |value, index|
  
    # we will add a conditional pry to 
    # start a session when our value is nil 

    binding.pry if value.nil?
    new_hash["#{value}"] = index
  end 
  new_hash
end 

We only covered some of the basic things pry can do — imagine how much more you can accomplish when you use Pry with Rails.

Yes, pry is awesome, but it doesn't just end here! In the second part of this series, we will combine pry with steering and pry with rails.

Discover and read more posts from Amodu Temitope
get started
Enjoy this post?

Leave a like and comment for Amodu

4
Be the first to share your opinion

Get curated posts in your inbox

Read more posts to become a better developer