Rubyists, It’s Time to PRY Yourself Off IRB!
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(#
. 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 LineItem
s, 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 edit
s
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!