Skip to content

Instantly share code, notes, and snippets.

@acastro2
Created May 11, 2020 16:16
Show Gist options
  • Save acastro2/ff61bb2a3dabcc4b71adaca0bd6d1d6b to your computer and use it in GitHub Desktop.
Save acastro2/ff61bb2a3dabcc4b71adaca0bd6d1d6b to your computer and use it in GitHub Desktop.
Pry Tutorial

Pry

Nothing Works the Way You Think It Does. It's a familiar scene: the code doesn't work. Not only does it not work, but it makes no sense whatsoever that it doesn't work. You've checked the code several times, and it's just not possible that it's broken. But it is.

In Ruby, you can debug and test your code using IRB, short for Interactive Ruby, a quick way to explore the Ruby programming language and try out code without creating a file. IRB is a Read-Eval-Print Loop, or REPL (7 Minutes), a tool offered by many modern programming languages. To use it, you launch the irb executable and type your Ruby code at the prompt. IRB evaluates the code you type and displays the results.

However, IRB is not without its limitations, and this is where Pry comes in. Pry present's itself as a powerful IRB alternative and runtime developer console for Ruby. However, it is much, much more than that.

Required Resources

  • Pry (11 Minutes): We use a library called pry, which is a replacement for irb. You'll want to be familiar with it; it's one of the most important debugging tools we use.

Tutorial

Installation

gem install pry pry-doc

Install both pry and pry-doc. The latter provides MRI Core documentation and source code.

Run Pry from Command Line

pry
[1] pry(main)> 1 + 1
=> 2

Simply run pry from the command line.

Pry Commands

Pry has many methods; this guide presents with the most important ones to you get familiar with and understand your whereabouts.

help

The first most important command is the help.

[1] pry(main)> help
Help
  help               Show a list of commands or information about a specific command.

Context
  cd                 Move into a new context (object or scope).
  find-method        Recursively search for a method within a class/module or the current namespace.
  ls                 Show the list of vars and methods in the current scope.
  pry-backtrace      Show the backtrace for the pry session.
  raise-up           Raise an exception out of the current pry instance.
  reset              Reset the repl to a clean state.
  watch              Watch the value of an expression and print a notification whenever it changes.
  whereami           Show code surrounding the current context.
  wtf?               Show the backtrace of the most recent exception.
...

I strongly advise you to read all the commands and try those out.

If you have questions about a particular command, you can also use:

[1] pry(main)> help show-doc
Usage:   show-doc [OPTIONS] [METH]
Aliases: ?

Show the documentation for a method or class. Tries instance methods first and
then methods by default.

show-doc hi_method # docs for hi_method
show-doc Pry       # for Pry class
show-doc Pry -a    # for all definitions of Pry class (all monkey patches)

    -s, --super             Select the 'super' method. Can be repeated to traverse the ancestors
    -l, --line-numbers      Show line numbers
    -b, --base-one          Show line numbers but start numbering at 1 (useful for `amend-line` and `play` commands)
    -a, --all               Show all definitions and monkeypatches of the module/class
    -h, --help              Show this message.

Live help system - Pry Wiki (10 Minutes).

show-doc

The ability to retrieve method documentation is essential when learning a new library or code base. And the ability to read the documentation in a REPL environment, where you can interact with the methods on live code is particularly useful. Pry does this without relying on any external utilities. It simply extracts what it needs at runtime from the source file. To retrieve a method or class's documentation, use the show-doc command. You can use the show-doc command in instances or without instances using the class definition. Finally, Pry provides a convenient shortcut for show-doc?:

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

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

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.collect {|x| x + "!"}           #=> ["a!", "b!", "c!", "d!"]
   a.map.with_index {|x, i| x * i}   #=> ["", "b", "cc", "ddd"]
   a                                 #=> ["a", "b", "c", "d"]

WARNING: the show-doc command is deprecated. It will be removed from future Pry versions.
Please use 'show-source' with the -d (or --doc) switch instead
Example: show-source Array#map -d

Documentation browsing - Pry Wiki (6 Minutes).

show-source

One of Pry's killer features is its ability to display source code for methods and classes. Pry does this without relying on any external utilities. Source browsing is an invaluable tool when you are coming to grips with a new code base, and it comes into its own when the library you are exploring has little or no documentation.

The show-source command is capable of showing source code for classes/modules and methods. Simply typing show-source method_name pulls the source for the method and display it with syntax highlighting.

[1] pry(main)> show-source Array#map

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

static VALUE
rb_ary_collect(VALUE ary)
{
    long i;
    VALUE collect;

    RETURN_SIZED_ENUMERATOR(ary, 0, 0, ary_enum_length);
    collect = rb_ary_new2(RARRAY_LEN(ary));
    for (i = 0; i < RARRAY_LEN(ary); i++) {
        rb_ary_push(collect, rb_yield(RARRAY_AREF(ary, i)));
    }
    return collect;
}

Source browsing - Pry Wiki (8 Minutes).

State navigation with cd and ls

The cd command is used to move into a new object (or scope) inside a Pry session. When inside the new scope, it becomes the self for the session, and all commands and methods will operate on this new self.

As in UNIX shells, you use cd .. to go back to the previous scope, cd / to return to the top-level scope for the Pry session (usually main but does not have to be) and cd - to switch between last two scopes.

This extended cd syntax is known as "object path syntax".

[1] pry(main)> arr = [1, 2, 3]
=> [1, 2, 3]
[2] pry(main)> cd arr
[3] pry(#<Array>):1> self
=> [1, 2, 3]
[4] pry(#<Array>):1> cd ..
[5] pry(main)>

The ls command is essentially a unified wrapper to a number of Ruby's introspection mechanisms, including (but not limited to) the following methods: methods, instance_variables, constants, local_variables, instance_methods, class_variables and all the various permutations thereof.

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

By default typing ls shows you the local variables defined in the current context and any public methods or instance variables defined on the current object.

State navigation - Pry Wiki (9 Minutes).

Debugging with Pry

Setting the default editor

Simply add export EDITOR='code -w' to your .zshrc or .bashrc file. You can also use .pryrc (2 Minutes) file.

Invoking on a binding

The standard (and recommended) way to invoke Pry at runtime is to use binding.pry. Starting Pry this way ensures that the session inherits all local variables and other relevant states. It also causes the whereami command to be invoked automatically - and so the surrounding context of the session (and few lines either side of the invocation line) is displayed for the user.

Note that we can put binding.pry anywhere in our program at the point we want the session to start, and the self of the session will be the self at that point.

Create a test.rb file and put the code below on it:

require 'pry'

class A
  def hello() puts "hello world!" end
end

a = A.new

# start a REPL session
binding.pry

# program resumes here (after pry session)
puts "program resumes here."

Now let's execute and edit the class using Pry:

pry test.rb
[1] pry(main)> a.hello
hello world!
=> nil
[2] pry(main)> def a.goodbye
[2] pry(main)*   puts "goodbye cruel world!"
[2] pry(main)* end
=> :goodbye
[3] pry(main)> a.goodbye
goodbye cruel world!
=> nil
[4] pry(main)> exit
program resumes here.

Now check the test.rb file and see that the goodbye method was added only during runtime, it is not persisted in the file.

Runtime invocation - Pry Wiki (6 Minutes).

whereami

When invoking a runtime session on a Binding, the whereami command is automatically executed. However, the command can also be invoked explicitly by typing whereami in the REPL.

pry test.rb
[1] pry(main)> cd a
[2] pry(#<A>):1> whereami
Inside #<A>.

The _ex_ variable and wtf method

Pry gives you access to the most recently caught exception in a local variable called _ex_. This persists until another exception is raised so that it can be used for detailed digging. The _ex_ variable is a special local (4 Minutes).

pry
[1] pry(main)> 4/0
ZeroDivisionError: divided by 0
from (pry):1:in `/'
[2] pry(main)> _ex_.message
=> "divided by 0"
[3] pry(main)> _ex_.backtrace
=> ["(pry):1:in `/'",
 "(pry):1:in `__pry__'",
...

As an alternative to using _ex_.backtrace you can use the wtf command to display a few lines of the backtrace for the most recent exception. If you want to see more lines, add more question marks ? or exclamation marks !. To see the entire backtrace, pass the -v (--verbose) flag, e.g.: wtf -v.

[4] pry(main)> wtf
Exception: ZeroDivisionError: divided by 0
--
0: (pry):1:in `/'
1: (pry):1:in `__pry__'
2: /Users/acastro/.gem/ruby/2.5.1/gems/pry-0.13.1/lib/pry/pry_instance.rb:290:in `eval'
3: /Users/acastro/.gem/ruby/2.5.1/gems/pry-0.13.1/lib/pry/pry_instance.rb:290:in `evaluate_ruby'
4: /Users/acastro/.gem/ruby/2.5.1/gems/pry-0.13.1/lib/pry/pry_instance.rb:659:in `handle_line'

[5] pry(main)> wtf?
Exception: ZeroDivisionError: divided by 0
--
0: (pry):1:in `/'
1: (pry):1:in `__pry__'
2: /Users/acastro/.gem/ruby/2.5.1/gems/pry-0.13.1/lib/pry/pry_instance.rb:290:in `eval'
3: /Users/acastro/.gem/ruby/2.5.1/gems/pry-0.13.1/lib/pry/pry_instance.rb:290:in `evaluate_ruby'
4: /Users/acastro/.gem/ruby/2.5.1/gems/pry-0.13.1/lib/pry/pry_instance.rb:659:in `handle_line'
5: /Users/acastro/.gem/ruby/2.5.1/gems/pry-0.13.1/lib/pry/pry_instance.rb:261:in `block (2 levels) in eval'
6: /Users/acastro/.gem/ruby/2.5.1/gems/pry-0.13.1/lib/pry/pry_instance.rb:260:in `catch'
7: /Users/acastro/.gem/ruby/2.5.1/gems/pry-0.13.1/lib/pry/pry_instance.rb:260:in `block in eval'
8: /Users/acastro/.gem/ruby/2.5.1/gems/pry-0.13.1/lib/pry/pry_instance.rb:259:in `catch'
9: /Users/acastro/.gem/ruby/2.5.1/gems/pry-0.13.1/lib/pry/pry_instance.rb:259:in `eval'

[6] pry(main)> wtf?!?!?!
Exception: ZeroDivisionError: divided by 0
--
 0: (pry):1:in `/'
 1: (pry):1:in `__pry__'
 2: /Users/acastro/.gem/ruby/2.5.1/gems/pry-0.13.1/lib/pry/pry_instance.rb:290:in `eval'
 3: /Users/acastro/.gem/ruby/2.5.1/gems/pry-0.13.1/lib/pry/pry_instance.rb:290:in `evaluate_ruby'
 4: /Users/acastro/.gem/ruby/2.5.1/gems/pry-0.13.1/lib/pry/pry_instance.rb:659:in `handle_line'
 5: /Users/acastro/.gem/ruby/2.5.1/gems/pry-0.13.1/lib/pry/pry_instance.rb:261:in `block (2 levels) in eval'
 6: /Users/acastro/.gem/ruby/2.5.1/gems/pry-0.13.1/lib/pry/pry_instance.rb:260:in `catch'
 7: /Users/acastro/.gem/ruby/2.5.1/gems/pry-0.13.1/lib/pry/pry_instance.rb:260:in `block in eval'
 8: /Users/acastro/.gem/ruby/2.5.1/gems/pry-0.13.1/lib/pry/pry_instance.rb:259:in `catch'
 9: /Users/acastro/.gem/ruby/2.5.1/gems/pry-0.13.1/lib/pry/pry_instance.rb:259:in `eval'
10: /Users/acastro/.gem/ruby/2.5.1/gems/pry-0.13.1/lib/pry/repl.rb:77:in `block in repl'
11: /Users/acastro/.gem/ruby/2.5.1/gems/pry-0.13.1/lib/pry/repl.rb:67:in `loop'
12: /Users/acastro/.gem/ruby/2.5.1/gems/pry-0.13.1/lib/pry/repl.rb:67:in `repl'
13: /Users/acastro/.gem/ruby/2.5.1/gems/pry-0.13.1/lib/pry/repl.rb:38:in `block in start'
14: /Users/acastro/.gem/ruby/2.5.1/gems/pry-0.13.1/lib/pry/input_lock.rb:61:in `__with_ownership'
15: /Users/acastro/.gem/ruby/2.5.1/gems/pry-0.13.1/lib/pry/input_lock.rb:78:in `with_ownership'
16: /Users/acastro/.gem/ruby/2.5.1/gems/pry-0.13.1/lib/pry/repl.rb:38:in `start'
17: /Users/acastro/.gem/ruby/2.5.1/gems/pry-0.13.1/lib/pry/repl.rb:15:in `start'
18: /Users/acastro/.gem/ruby/2.5.1/gems/pry-0.13.1/lib/pry/pry_class.rb:191:in `start'
19: /Users/acastro/.gem/ruby/2.5.1/gems/pry-0.13.1/lib/pry/cli.rb:119:in `start'
20: /Users/acastro/.gem/ruby/2.5.1/gems/pry-0.13.1/bin/pry:13:in `<top (required)>'
21: /Users/acastro/.gem/ruby/2.5.1/bin/pry:23:in `load'
22: /Users/acastro/.gem/ruby/2.5.1/bin/pry:23:in `<main>'
[9] pry(main)>

Exercise

  1. If you've got any nagging issues inside your code, now is a great time to debug it using binding.pry.
  2. Consider switching your default console command to pry in your project. Change the console default to pry.

Additional Resources

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment