Rubyists, It’s Time to PRY Yourself Off IRB!

Tweet

tOTeiZo

Every Rubyist knows about irb. The Interactive Ruby Shell is essentially a REPL (read-eval-print loop). Type in some expression, and the result gets returned immediately. So how does Pry fit in? Pry bills itself as a powerful alternative to the standard IRB shell. But it is much, much more than that.

In this article, I will guide you through some of the best features of Pry. Many of these features will alter your workflow in fundamental ways. The more you learn about Pry, the more you’ll wonder where it has been all this time.

Let’s get prying!

Installation

Getting Pry is simple:

% gem install pry pry-doc                                                                       
Fetching: pry-0.9.12.2.gem (100%)
Successfully installed pry-0.9.12.2
Parsing documentation for pry-0.9.12.2
Installing ri documentation for pry-0.9.12.2
Fetching: pry-doc-0.4.6.gem (100%)
Successfully installed pry-doc-0.4.6
Parsing documentation for pry-doc-0.4.6
Installing ri documentation for pry-doc-0.4.6
2 gems installed

Here, we install both pry and pry-doc. pry-doc provides MRI Core documentation and source code. We will need this in the later examples.

Now, just to make sure that everything works:

% pry -v                                                                                                 
Pry version 0.9.12.2 on Ruby 2.0.0

As of this writing, I am using the latest version of Pry. Pry works fine on both Ruby 1.9 and Ruby 2.0. Please note that I’m using Ruby MRI.

Part I: Let’s Break into Some Code

Personally, I find that the best way to learn Pry is to work through some examples. In this section, we look at the tools Pry gives us to display documentation and source code. In the process, we will also see how Pry uses shell navigation commands to navigate object state.

Let’s launch our Pry session:

% pry                                                                                                     
[1] pry(main)>

Showing Documentation with show-doc

Let’s say I need to recall the difference between Array#map and Array#map!. The show-doc command makes this a snap:

[2] pry(main)> show-doc Array#map

From: array.c (C Method):
Owner: Array
Visibility: public
Signature: map()
Number of lines: 11

Invokes the given block once for each element of self.

Creates a new array containing the values returned by the block.

See also Enumerable#collect.

If no block is given, an Enumerator is returned instead.

   a = [ "a", "b", "c", "d" ]
   a.map { |x| x + "!" }   #=> ["a!", "b!", "c!", "d!"]
   a                       #=> ["a", "b", "c", "d"]

[3] pry(main)> show-doc Array#map!

From: array.c (C Method):
Owner: Array
Visibility: public
Signature: map!()
Number of lines: 10

Invokes the given block once for each element of self, replacing the
element with the value returned by the block.

See also Enumerable#collect.

If no block is given, an Enumerator is returned instead.

        a = [ "a", "b", "c", "d" ]
        a.map! {|x| x + "!" }
        a #=>  [ "a!", "b!", "c!", "d!" ]

Pry provides a very handy shortcut for show-doc?:

[3] pry(main)> ? Array#map!

Navigating State with cd and ls

This is easily one of the coolest features of Pry. Let’s say I have an array:

[4] pry(main)> arr = [1, 2, 3]
=> [1, 2, 3]

We can cd into an object, like so:

[5] pry(main)> cd arr
[6] pry(#<Array>):1>

Notice how when we cd-ed into arr, the prompt changes to pry(#):1>. This tells us that the current object is an Array instance, denoted by the #< ...> notation. “

Now, try ls-ing.

[6] pry(#<Array>):1> ls
Enumerable#methods:
  all?            each_entry        find_all  max      minmax_by     sort_by
  any?            each_slice        flat_map  max_by   none?
  chunk           each_with_index   grep      member?  one?
  collect_concat  each_with_object  group_by  min      partition
  detect          entries           inject    min_by   reduce
  each_cons       find              lazy      minmax   slice_before
Array#methods:
  &            count       include?            reject                slice
  *            cycle       index               reject!               slice!
  +            delete      insert              repeated_combination  sort
  -            delete_at   inspect             repeated_permutation  sort!
  <<           delete_if   join                replace               sort_by!
  <=>          drop        keep_if             reverse               take
  ==           drop_while  last                reverse!              take_while
  []           each        length              reverse_each          to_a
  []=          each_index  map                 rindex                to_ary
  assoc        empty?      map!                rotate                to_s
  at           eql?        pack                rotate!               transpose
  bsearch      fetch       permutation         sample                uniq
  clear        fill        place               select                uniq!
  collect      find_index  pop                 select!               unshift
  collect!     first       pretty_print        shelljoin             values_at
  combination  flatten     pretty_print_cycle  shift                 zip
  compact      flatten!    product             shuffle               |
  compact!     frozen?     push                shuffle!
  concat       hash        rassoc              size
self.methods: __pry__
locals: _  __  _dir_  _ex_  _file_  _in_  _out_  _pry_

What else does ls do? Let’s ask Pry:

[14] (pry) main: 0> ls -h
Usage: ls [-m|-M|-p|-pM] [-q|-v] [-c|-i] [Object]
       ls [-g] [-l]

ls shows you which methods, constants and variables are accessible to Pry. By default it shows you the local variables defined in the current shell, and any public methods or instance variables defined on the current object.

The colours used are configurable using Pry.config.ls.*_color, and the separator is Pry.config.ls.separator.

Pry.config.ls.ceiling is used to hide methods defined higher up in the inheritance chain, this is by default set to [Object, Module, Class] so that methods defined on all Objects are omitted. The -v flag can be used to ignore this setting and show all methods, while the -q can be used to set the ceiling much lower and show only methods defined on the object or its direct class.


options:

    -m, --methods               Show public methods defined on the Object (default)
    -M, --instance-methods      Show methods defined in a Module or Class
    -p, --ppp                   Show public, protected (in yellow) and private (in green) methods
    -q, --quiet                 Show only methods defined on object.singleton_class and object.class
    -v, --verbose               Show methods and constants on all super-classes (ignores Pry.config.ls.ceiling)
    -g, --globals               Show global variables, including those builtin to Ruby (in cyan)
    -l, --locals                Show locals, including those provided by Pry (in red)
    -c, --constants             Show constants, highlighting classes (in blue), and exceptions (in purple)
    -i, --ivars                 Show instance variables (in blue) and class variables (in bright blue)
    -G, --grep                  Filter output by regular expression
    -h, --help                  Show this message.

Let’s go back to our arr object. Once we are in an object, we can call its methods directly, like so:

[7] pry(#<Array>):1> min
=> 1
[8] pry(#<Array>):1> max
=> 3
[9] pry(#<Array>):1> reverse
=> [3, 2, 1]

Viewing the Source with show-method

Ever wondered how Array#map! is implemented? Recall that since we are already in an object, we can simply use show-source map! instead of show-source Array#map!.

[10] pry(#<Array>):1> show-source map!

From: array.c (C Method):
Owner: Array
Visibility: public
Number of lines: 12

static VALUE
rb_ary_collect_bang(VALUE ary)
{
    long i;

    RETURN_SIZED_ENUMERATOR(ary, 0, 0, rb_ary_length);
    rb_ary_modify(ary);
    for (i = 0; i < RARRAY_LEN(ary); i++) {
                        rb_ary_store(ary, i, rb_yield(RARRAY_PTR(ary)[i]));
    }
    return ary;
}

MRI is implemented in C, which explains the slightly cryptic listing. To get a Ruby code listing, you would have to switch to Rubinius, since most of its core libraries are built in Ruby. Studying the Rubinius code base with Pry is an excellent way to learn about Ruby implementation details in Ruby instead of C.

There are a few other ways of achieving the same result. But before that, let’s go up one level:

[11] pry(#<Array>):1> cd ..
[12] pry(main)>

The following are all equivalent:

[13] pry(main)> show-source Array#map!
[14] pry(main)> show-source arr.map!

Just like show-doc and ?, the equivalent for show-source is $:

[15] pry(main)> $ Array#map!
[16] pry(main)> $ arr.map!

Part II: Diving deeper into Pry’s Debugging Facilities

So far, we’ve seen that Pry provides us with a very nice help system, along with ways to navigate and peek into the state of our objects. But Pry can do much, much more. In this section, we take a look at Pry’s debugging facilities.

Prerequisites

We need to tell Pry what our default editor should be. Create a file called .pryrc in your home directory. Since I love Vim:

Pry.config.editor = 'vim'

If you want to follow along, now would be a great time to grab the example file:

class Order
  def initialize
    @line_items = []
  end

  def add_line_item(line_item)
    @line_items << line_item
  end

  def total
    subtotals = @line_items.each { |li| li.quantity * li.price }
    subtotals.reduce(:+)
  end
end

class LineItem
  attr_reader :quantity, :price

  def initialize(quantity, price)
    @price    = price
    @quantity = quantity
  end
end

order = Order.new
order.add_line_item LineItem.new(2, 3.00)
order.add_line_item LineItem.new(4, 1.00)
puts order.total

Here is a very simple Order and LineItem class. An Order can add many LineItems, each of which holds a quantity and price. An Order object can also calculate the total.

Run this program and watch it crash:

% ruby order.rb
order.rb:12:in `each': undefined method `+' for #<LineItem:0x007fb4e2013350 @price=3.0, @quantity=2> 
(NoMethodError)
        from order.rb:12:in `reduce'
        from order.rb:12:in `total'
        from order.rb:28:in `<main>'

While this error might be trivial to solve, please bear with me – because here comes the fun part.

Setting Breakpoints with binding.pry

Since we know our program crashes on line 14, let’s put a breakpoint one line before that. Modify the totals method as follows:

def total
  subtotals = @line_items.each { |li| li.quantity * li.price }
  binding.pry # <-- Add this
  subtotals.reduce(:+)
end

This time, we run our program slightly differently. Note the -r pry flag:

% ruby -r pry  order.rb

From: /Users/rambo/Desktop/store/order.rb @ line 14 Order#total:

    12: def total
    13:   subtotals = @line_items.each { |li| li.quantity * li.price }
 => 14:   binding.pry
    15:     subtotals.reduce(:+)
    16: end

[1] pry(#<Order>)>

What just happened? Firstly, your program did not crash. Instead, a Pry session was launched, and the execution of the program stops at where we placed binding.pry – our breakpoint. After that, we see pry(#)>, which means that we are in an Order instance. This is because binding.pry causes the Pry session to begin within the scope of our Order instance.

Let’s recall what ls does:

[1] pry(#<Order>)> ls
Order#methods: add_line_item  line_items  total
instance variables: @line_items
locals: _  __  _dir_  _ex_  _file_  _in_  _out_  _pry_  subtotals

Are our line_items properly populated?

[2] pry(#<Order>)> line_items
=> [#<LineItem:0x007f80d25b62b0 @price=3.0, @quantity=2>,
 #<LineItem:0x007f80d25b6288 @price=1.0, @quantity=4>]

So far, so good. Take a closer look at the total method. Since binding.pry occurs after the subtotals variable, we can certainly access that:

[3] pry(#<Order>)> subtotals
=> [#<LineItem:0x007f80d25b62b0 @price=3.0, @quantity=2>,
 #<LineItem:0x007f80d25b6288 @price=1.0, @quantity=4>]

Aha! The result of subtotals is exactly the same as line_items. We should have used map instead of each! Remember how we configured our editor previously? Now we will put that to good use.

Making edits

Pry allows you to edit a method without ever leaving the session. We now know we must replace each with a map. So let’s do that:

[4] pry(#<Order>)> edit total

You will notice vim (or whatever editor your configured in .pryrc) will launch, with the cursor at the first line of total. Make the necessary changes:

def total
  subtotals = @line_items.map { |li| li.quantity * li.price }
  binding.pry
  subtotals.reduce(:+)
end

Exit the editor, and you would be brought back to the Pry session. Now, let’s see what does subtotals contain:

[1] pry(#<Order>)> subtotals
=> [6.0, 4.0]

Nice! When we exited from our editor, Pry automatically reloaded the file, and again stopped at binding.pry.

Running Shell Commands from Pry

Before removing binding.pry, we can check if the line after binding.pry works. Since I know the line number, I will go ahead and run the line:

[2] pry(#<Order>)> play -l 15
10.0

Success! Now we can go ahead and remove binding.pry. But before we do, let’s see what changes we have made:

[3] pry(#<Order>)> .git diff

Pry has the ability to run arbitrary shell commands. All you have to do is prefix the command with a . (dot), like what we did to git diff:

diff --git a/order.rb b/order.rb
index c05aa7d..823ccac 100644
--- a/order.rb
+++ b/order.rb
@@ -10,7 +10,9 @@ class Order
   end

   def total
-    subtotals = @line_items.each { |li| li.quantity * li.price }
+    subtotals = @line_items.map { |li| li.quantity * li.price }
     subtotals.reduce(:+)
   end
 end

@@ -26,4 +28,4 @@ end
 order = Order.new
 order.add_line_item LineItem.new(2, 3.00)
 order.add_line_item LineItem.new(4, 1.00)
 puts order.total

View Stack Traces with wtf?

Let’s intentionally cause some trouble. Modify the code by adding another LineItem. To save us a bit of time, let us also put binding.pry before that line.

order = Order.new
order.add_line_item LineItem.new(2, 3.00)
order.add_line_item LineItem.new(4, 1.00)
binding.pry
order.add_line_item LineItem.new(1/0, 100)
puts order.total

Then we run the program:

% ruby -r pry order.rb                                                 

From: /store/order.rb @ line 30 :

    25: end
    26:
    27: order = Order.new
    28: order.add_line_item LineItem.new(2, 3.00)
    29: order.add_line_item LineItem.new(4, 1.00)
 => 30: binding.pry
    31: order.add_line_item LineItem.new(1/0, 1.00)
    32: puts order.total

Let’s go ahead and play line 31:

[1] pry(#<Order>)> play -l 31

As expected, the program crashes. To see a detailed stack trace, use the wtf? command:

[2] pry(#<Order>)> wtf?
Exception: ZeroDivisionError: divided by 0
--
0: (pry):6:in `/'
1: (pry):6:in `total'
3: /usr/local/var/rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/pry-0.9.12.2/lib/pry/pry_instance.rb:328:in `evaluate_ruby'
...
9: /usr/local/var/rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/pry-0.9.12.2/lib/pry/pry_instance.rb:231:in `catch'

To see an even longer stack trace, simply append more ?. For example, wtf??? yields 30 lines. The Pry developers sure do have a sense of humour.

Analysing Stack Traces with cat --ex

Pry still has yet another trick up its sleeve. cat --ex directs you to the actual line which threw the exception:

[3] pry(main)> cat --ex

Exception: ZeroDivisionError: divided by 0
--
From: (pry) @ line 1 @ level: 0 of backtrace (of 15).

 => 1: order.add_line_item LineItem.new(1/0, 1.00)

cat --ex also takes in a number as an additional argument, which essentially walks you up the stack trace. This feature is very useful for debugging and tracing larger programs. (Anyone who has done any non-trivial Rails applications would definitely appreciate this feature.)

Wrapping Up

I hope this article has convinced you to give Pry a try (it has a nice ring to it, doesn’t it?). There is still quite a bit of material that was not covered. For example, we have not looked at how we could replace the Rails console with Pry. Also, we have not taken a look at Pry’s plugins, which add even more powerful features to an already impressive feature set.

So far, the only downside to Pry is the time it takes to launch a session. However, a slight delay is a small price to pay for all the power and flexibility Pry gives to you.

Do check out the Official Pry Wiki. The documentation is very comprehensive. There are also several links to where you can learn more about Pry, so I will not repeat it here.

Happy Prying!

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • Imran Latif

    Awesome article. I’ am definitely going to try pry after reading this.

    Thanks,

  • Anonymous

    Pry is currently my goto debugging and interactive programming tool. I didn’t know about the editor configuration and now my workflow can be even better instead of jumping around several tmux windows I can just edit stuff on the fly.

  • http://joshuagreenwood.tumblr.com/ Josh Greenwood

    FYI, you are missing the ‘@’ before line_items when you check to see if your line items are properly populated. Thanks for the post!

  • americos

    Good article.
    Just a few typos/things as feedback: 1) when you talk about looking at the contents of line_items you have to use @line_items in Pry, text currently says only line_items. 2) Don’t forget to mention that in order to have the .git diff command running you have have a git repository in that directory (simple thing, but I’m sure more than one will experience the error and say what the heck)