Turning IRB on its head with Pry

IRB is a great tool and is perfect for experimenting with small code samples and testing out new ideas. It has some difficulty however when the code samples become a bit larger or you’d like to start an IRB session half-way through a method, for example.

Pry, in some sense, is IRB turned on its head. Instead of having to bring your code to a REPL session (as with IRB) you instead bring a REPL session to your code, see the following:

# test.rb
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."

We then run ruby test.rb from the command line and the following REPL session begins:

Beginning Pry session for main
pry(main)> a.hello
hello world!
=> nil
pry(main)> def a.goodbye
pry(main)*   puts "goodbye cruel world!"
pry(main)* end
=> nil
pry(main)> a.goodbye
goodbye cruel world!
=> nil
pry(main)> exit
Ending Pry session for main
program resumes here.

There are a few things to note from above:

  • The first is that local variables are available to a Pry session (locals are not available to an irb session when using `irb -r`).
  • The second is that when you end a Pry session it returns to the running program; this makes Pry particularly useful for debugging.

Debugging

Pry sits somewhere between using `puts` statements to debug and using a bona fide debugger. It effectively opens up an IRB-like session at the place it’s called and makes all the program state at that point available.

In the example below, the code (sans the `binding.pry`) works as required in Ruby 1.8 but in 1.9 it returns the wrong result – notably check_array([]) displays "ary has content"; whereas we want it to output "ary is empty!" We start a Pry session right after the line b = *ary to try to determine the problem in Ruby 1.9:

def check_array(ary)
  b = *ary

  # invoking Pry here for debugging
  binding.pry

  if !b
    puts "ary is empty!"
  else
    puts "ary has content"
  end
end

check_array([])

When running the above code the following Pry session starts up:

Beginning Pry session for main
pry(main)> show-method
def check_array(ary)
  b = *ary

  binding.pry

  if !b
    puts "ary is empty!"
  else
    puts "ary has content"
  end
end
=> nil
pry(main)> b
=> []
pry(main)> test = *[]
=> []
pry(main)> b = !ary.empty?
=> false
pry(main)> exit
Ending Pry session for main
ary is empty!

From above the first thing we do is display the code for the method we’re debugging – Pry’s `show-method` command does this.

We next experiment and find that the behaviour of *[] has changed in 1.9 (in 1.8 it returned nil) and this must be the cause of our problems. We then come up with a new test b = !b.empty? and let the program continue (by typing `exit`) with this new value of b. It now outputs the expected result.

Note that any changes to state we make in a Pry session persist during the lifetime of the program. This fact also makes Pry useful for interactively modifying runtime state.

Interactively modifying runtime state

It may be convenient to open a Pry session in the middle of a running program. The example below is of a game where we are using Pry to increase the lunar lander’s fuel; we also do a bit of exploration of the runtime state to show off some features of Pry.

Beginning Pry session for #
pry(#)> ls
[:_, :_pry_,:@state, :@frame_counter, :@playgame]
=> nil
pry(#)> cd @playgame
Beginning Pry session for #
pry(#):1> ls
[:_, :_pry_, :@map, :@font,  :@wind, :@objects, :@lander]
=> nil
pry(#):1> cd @lander
Beginning Pry session for #
pry(#):2> @fuel = 300000
=> 300000
pry(#):2> nesting
Nesting status:
--
0. # (Pry top level)
1. #
2. #
=> nil
pry(#):2> cd ..
Ending Pry session for #
=> nil
pry(#):1> ls --methods
[:__binding_impl__, :draw, :initialize, :lander, :level, :map]
=> nil
pry(#):1> cd map
Beginning Pry session for #
pry(#):2> ls
[:_, :_pry_, :@nebula, :@nebula_theta, :@moonscape]
=> nil
pry(#):2> exit-all
Ending Pry session for #
Ending Pry session for #
Ending Pry session for #

From above, Pry makes it easy to navigate runtime state. You can pop in and out of objects, nesting sessions as deeply as you like. The `cd` and `ls` commands are provided to make this navigation seem familiar and natural.

Customizability

Pry is also easily customizable – you can trivially set the input for a Pry session to objects other than `Readline` and `$stdin`; and likewise set the output object to something other than `$stdout`.

Many other features of Pry can also be customized making Pry a perfect choice for implementing custom shells. See Customizing Pry for more information.

An IRB Alternative

Pry can be invoked from the command line using the `pry` executable. It can then be used as an alternative to IRB. Many of the IRB command line options are supported. Type `pry –help` at the command line for more information.

Conclusion

Pry is an IRB-alike that can be invoked at any time and anywhere in the program and on any receiver; it can also receive input from anywhere and send output to anywhere. The examples shown here illustrate just some of the functionality of Pry, read the Wiki for the full story.

About these ads
Tags: , ,

13 Comments to “Turning IRB on its head with Pry”

  1. Nice tool!

    The idea of cd and ls are very good. What other features does it offer and ruby-debug does not?

    • Hi,

      Pry isn’t really a debugger, though it can be used in this way. I originally wrote Pry as a REPL for image editing software (you can see a simplified version of this project in examples/example_image_edit.rb).

      Anyway :) Some features Pry has that ruby-debug may not are the `show_method` and `show_doc` commands; also the `nesting` and `jump_to` commands for managing nested sessions.

      Another feature that Pry has is its high degree of customizability and flexibility: many aspects of Pry can be customized and customized easily.

      Check out the documentation for more info on all this. Thanks for your interest :)

      • The idea of writing a REPL with the assistance of Ruby’s binding methods is cool.

        It is not new; http://rubyforge.org/projects/ruby-breakpoint/ was one of the first in Ruby to do this as far as I can remember. (I’m not sure though that this code has been maintained over the years.)

        In some respects I’d consider these debuggers: they help you dynamically introspect code and find bugs.

        However here are in my view the essential differences of a more complete or traditional debugger.

        * Being able to look inspect/change scope along the call stack. For example, my caller’s local variables and it’s caller’s local variables
        * Being able to step code and/or being able to run up to some point in the program or until some condition, without having to modify source code.

        The difficulty behind inspecting call frame variables really lies in the difficulty in getting that in the MRI interpreters. For MRI 1.9, the most satisfying way to get this is to patch the Runtime to be able to get that information. That’s the motivation behind http://github.com/rocky/rb-threadframe. However for Rubinius, you can get this without any modification to Rubinius via: Rubinus.VM::backtrace

        In some limited cases, one may be able to ameliorate not having being about to step without modifying code in a special cirucumstances. Suppose you have a program that is itself a REPL-like program. ruby-debug and the rewrite of it, trepanning are in fact REPLs. In the trepanning series of debuggers, each debugger command is in its own file. So suppose I find an error in one of the debugger commands, like “up”. I could add my Pry call to it and then inside the debugger, eval the Ruby command “load command/up.rb”. And then I would issue the “up” command and I’d then be in Pry.

        That said, to me it just isn’t as nice as being able to step or set a temporary or perminant breakpoint or stop when a condition is met.

        One final thing though about the extensibility remark. ruby-debug and the trepanning series in fact are pretty flexible and can let you add debugger commands.
        Underneath what’s needed is there in both trepanning and ruby-debug. It is not a very polished interface because it’s not been something folks have expressed interest in. But see load_debugger_commands() of https://github.com/rocky/rbx-trepanning/blob/master/processor/load_cmds.rb

  2. Great idea! Can’t wait to try it out.

  3. Great tool!

    I was always looking for something like this. Using a REPL session inside of my own programs is great for doing some interactive tests.

  4. A very nice tool indeed. Do you think it can be extended to be a REPL for live rack-based web applications? Ideally I mean something like lisp’s swank.

  5. Wow, Baninster, you really outdo you this time.

    But your grenades are of wrong system.

    ╔╗╔═╦╗
    ║╚╣║║╚╗
    ╚═╩═╩═╝

  6. Huh, that looks interesting. Do you think there is some way to use Pry instead of IRB with ‘rails console’?

  7. I put together a small gem that allows pry to be called from inside of ruby-debug. It seems to work so far, but I’d be really interested in feedback (especially from 1.8 users as I’m mostly using 1.9 now).

    Also, I haven’t looked too far into the internals of ruby-debug’s commands, so I’m sure there’s room for improvement. Given that Pry’s command API seems clearer than IRB’s, adding features should be pretty straight forward.

    Thanks for your hard-work! I’m already using it and am really impressed.

    https://github.com/AndrewO/ruby-debug-pry

  8. $ alias irb=pry

    thanks man :)

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: