You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
340 lines
8.1 KiB
340 lines
8.1 KiB
---
|
|
layout: post
|
|
title: Debugging Ruby with the Pry debugger
|
|
category: ruby
|
|
tags: [ruby, pry, debugging, code]
|
|
summary: What is Pry? How does it help me debug my Ruby?
|
|
---
|
|
|
|
## What is Pry?
|
|
Pry is an interactive shell that provides an approachable interface to debugging Ruby. Most notably it allows for the ability to transverse a running Ruby program much like one would a filesystem.
|
|
|
|
## Using Pry
|
|
|
|
Pry can be installed using `gem install pry` and be ran directly from your shell using `pry`.
|
|
|
|
By adding `require pry` to your Ruby code you can use Pry in your current project. Just requiring Pry will not start a session during execution however. For that, you must also include a breakpoint by adding `binding.pry` to your code.
|
|
|
|
> NOTE: I prefer to invoke Pry with a breakpoint on one line (`require 'pry'; binding.pry`).
|
|
> This prevents me from forgetting to remove the `require 'pry'` line from the top of the file and accidentally submitting a PR.
|
|
|
|
## Navigating the Shell
|
|
Once inside a Pry shell you can get a list of available commands by running `help`.
|
|
|
|
From that output you can see there are a plethora of executable commands. Don't be discouraged though, I made it for embarrassingly too long using only `ls`, `step`, `puts my_variable_name`, and `exit-program`
|
|
|
|
## Simple Usage Guide
|
|
One thing I wish I had had when starting to use Pry was a "Simple Usage Guide". Below is just such a thing.
|
|
|
|
### Create a Sample File
|
|
Create a file called `pry_example.rb` with the following contents
|
|
|
|
```ruby
|
|
def output_hash(input_hash)
|
|
puts input_hash
|
|
end
|
|
|
|
def set_baz(input_hash, input_value)
|
|
input_hash[:baz] = input_value
|
|
end
|
|
|
|
require 'pry'; binding.pry
|
|
|
|
some_hash = {}
|
|
some_hash[:foo] = 'bar'
|
|
|
|
output_hash(some_hash)
|
|
|
|
set_baz(some_hash, 'spam')
|
|
|
|
puts "Don't execute me"
|
|
```
|
|
|
|
### Starting Pry
|
|
Run `ruby pry_example.rb`
|
|
|
|
```
|
|
[~/Playground/Blog]$ ruby pry_example.rb
|
|
|
|
Frame number: 0/1
|
|
|
|
From: /home/jerry/Playground/Blog/pry_example.rb @ line 11 :
|
|
|
|
6: input_hash[:baz] = input_value
|
|
7: end
|
|
8:
|
|
9: require 'pry'; binding.pry
|
|
10:
|
|
=> 11: some_hash = {}
|
|
12: some_hash[:foo] = 'bar'
|
|
13:
|
|
14: output_hash(some_hash)
|
|
15:
|
|
16: set_baz(some_hash, 'spam')
|
|
|
|
[1] pry(main)>
|
|
```
|
|
|
|
As you can see we have halted execution and are at line 11 of our example program
|
|
|
|
### Using `step`
|
|
To execute the current line and proceed to the next type `step`
|
|
|
|
```
|
|
[1] pry(main)> step
|
|
|
|
From: /home/jerry/Playground/Blog/pry_example.rb @ line 12 :
|
|
|
|
7: end
|
|
8:
|
|
9: require 'pry'; binding.pry
|
|
10:
|
|
11: some_hash = {}
|
|
=> 12: some_hash[:foo] = 'bar'
|
|
13:
|
|
14: output_hash(some_hash)
|
|
15:
|
|
16: set_baz(some_hash, 'spam')
|
|
17:
|
|
|
|
[1] pry(main):1>
|
|
```
|
|
|
|
### Viewing Objects
|
|
To verify that line 11 was executed we can inspect the object by typing `some_hash`
|
|
|
|
```
|
|
[1] pry(main):1> some_hash
|
|
{}
|
|
[2] pry(main):1>
|
|
```
|
|
|
|
We can see here that `some_hash` is an empty array.
|
|
|
|
Run `step` again to execute line 12 and set `some_hash[:foo]` to `bar`
|
|
|
|
```
|
|
[2] pry(main):1> step
|
|
|
|
From: /home/jerry/Playground/Blog/pry_example.rb @ line 14 :
|
|
|
|
9: require 'pry'; binding.pry
|
|
10:
|
|
11: some_hash = {}
|
|
12: some_hash[:foo] = 'bar'
|
|
13:
|
|
=> 14: output_hash(some_hash)
|
|
15:
|
|
16: set_baz(some_hash, 'spam')
|
|
17:
|
|
18: puts "Don't execute me"
|
|
|
|
[2] pry(main)>
|
|
```
|
|
|
|
Now we can see that `some_hash` contains the symbol `:foo` which has the value of `bar`
|
|
|
|
```
|
|
[2] pry(main)> some_hash
|
|
{:foo=>"bar"}
|
|
[3] pry(main)>
|
|
```
|
|
|
|
### Using `whereami`
|
|
At any time during our execution we can show lines surrounding us with the command `whereami`
|
|
|
|
```
|
|
[3] pry(main)> whereami
|
|
|
|
From: /home/jerry/Playground/Blog/pry_example.rb @ line 14 :
|
|
|
|
9: require 'pry'; binding.pry
|
|
10:
|
|
11: some_hash = {}
|
|
12: some_hash[:foo] = 'bar'
|
|
13:
|
|
=> 14: output_hash(some_hash)
|
|
15:
|
|
16: set_baz(some_hash, 'spam')
|
|
17:
|
|
18: puts "Don't execute me"
|
|
|
|
[4] pry(main)>
|
|
```
|
|
|
|
After doing this we can see that the method `output_hash` is about to be executed
|
|
|
|
### Investigating Methods
|
|
We can view the `output_hash` method with `show-method output_hash`
|
|
|
|
```
|
|
[4] pry(main)> show-method output_hash
|
|
|
|
From: pry_example.rb @ line 1:
|
|
Owner: Object
|
|
Visibility: private
|
|
Number of lines: 3
|
|
|
|
def output_hash(input_hash)
|
|
puts input_hash
|
|
end
|
|
[5] pry(main)>
|
|
```
|
|
|
|
After viewing the method we can step into it using `step`
|
|
|
|
```
|
|
[5] pry(main)> step
|
|
|
|
From: /home/jerry/Playground/Blog/pry_example.rb @ line 2 Object#output_hash:
|
|
|
|
1: def output_hash(input_hash)
|
|
=> 2: puts input_hash
|
|
3: end
|
|
|
|
[5] pry(main)>
|
|
```
|
|
|
|
We can now see that we are inside the `output_hash` method
|
|
|
|
> Technically, we now know that `output_hash` is a procedure and not a method, but such semantics are out of scope for this post.
|
|
|
|
We can view local variables in `output_hash` by using `ls`
|
|
|
|
```
|
|
[5] pry(main)> ls
|
|
self.methods: inspect to_s
|
|
locals: _ __ _dir_ _ex_ _file_ _in_ _out_ _pry_ input_hash
|
|
[6] pry(main)>
|
|
```
|
|
|
|
We can inspect any of these variables with `ls` as well
|
|
|
|
Inspect `input_hash` by running `ls input_hash`
|
|
|
|
```
|
|
[6] pry(main)> ls input_hash
|
|
Enumerable#methods:
|
|
all? collect cycle drop_while each_slice redacted
|
|
any? collect_concat detect each_cons each_with_index redacted
|
|
chunk count drop each_entry each_with_object redacted
|
|
Hash#methods:
|
|
== clear default= delete_if redacted
|
|
[] compare_by_identity default_proc each redacted
|
|
[]= compare_by_identity? default_proc= each_key redacted
|
|
assoc default delete each_pair redacted
|
|
[7] pry(main)>
|
|
```
|
|
|
|
> NOTE: Output here is redacted to shorten line length
|
|
|
|
As you can see `input_hash` is indeed a Hash
|
|
|
|
Also listed are the available methods for both the Hash and Enumerable classes
|
|
|
|
Run `step` to execute the `puts input_hash` line and return to our main code
|
|
|
|
```
|
|
[7] pry(main)> step
|
|
{:foo=>"bar"}
|
|
|
|
From: /home/jerry/Playground/Blog/pry_example.rb @ line 16 :
|
|
|
|
11: some_hash = {}
|
|
12: some_hash[:foo] = 'bar'
|
|
13:
|
|
14: output_hash(some_hash)
|
|
15:
|
|
=> 16: set_baz(some_hash, 'spam')
|
|
17:
|
|
18: puts "Don't execute me"
|
|
|
|
[7] pry(main)>
|
|
```
|
|
|
|
As you can see the output of `output_hash` is sent to screen (`{:foo=>"bar"}`)
|
|
|
|
### Modifying Code During Runtime
|
|
Run `step` to go into the `set_baz` method
|
|
|
|
```
|
|
[7] pry(main)> step
|
|
|
|
From: /home/jerry/Playground/Blog/pry_example.rb @ line 6 Object#set_baz:
|
|
|
|
5: def set_baz(input_hash, input_value)
|
|
=> 6: input_hash[:baz] = input_value
|
|
7: end
|
|
|
|
[7] pry(main)>
|
|
```
|
|
|
|
Since we are in a running Ruby session we can modify code during runtime
|
|
|
|
Let's change the value of `input_value` to `eggs` by running `input_value = 'eggs'`
|
|
|
|
```
|
|
[7] pry(main)> input_value = 'eggs'
|
|
=> "eggs"
|
|
[8] pry(main)>
|
|
```
|
|
|
|
Now execute line 6 with `step`
|
|
|
|
```
|
|
[8] pry(main)> step
|
|
|
|
From: /home/jerry/Playground/Blog/pry_example.rb @ line 18 :
|
|
|
|
13:
|
|
14: output_hash(some_hash)
|
|
15:
|
|
16: set_baz(some_hash, 'spam')
|
|
17:
|
|
=> 18: puts "Don't execute me"
|
|
|
|
[8] pry(main)>
|
|
```
|
|
|
|
Now we can see `set_baz` set `some_hash[:bar]` to `eggs` by running `puts some_hash;`
|
|
|
|
```
|
|
[8] pry(main)> puts some_hash;
|
|
{:foo=>"bar", :baz=>"eggs"}
|
|
[9] pry(main)>
|
|
```
|
|
|
|
> TIP: Adding `;` to the end of Ruby commands will suppress extra shell output
|
|
|
|
### Exiting Pry
|
|
After running `whereami` we see that we do not want to execute line 18
|
|
|
|
So run `exit-program` to end our Pry session
|
|
|
|
```
|
|
[9] pry(main)> whereami
|
|
|
|
From: /home/jerry/Playground/Blog/pry_example.rb @ line 18 :
|
|
|
|
13:
|
|
14: output_hash(some_hash)
|
|
15:
|
|
16: set_baz(some_hash, 'spam')
|
|
17:
|
|
=> 18: puts "Don't execute me"
|
|
|
|
[10] pry(main)> exit-program
|
|
[~/Playground/Blog]$
|
|
```
|
|
|
|
## Conclusion
|
|
As you can see, with just a few simple commands we can do some valuable debugging of our code. I personally use Pry daily in my current job (*shakes fist at Ruby's `nil` errors*)
|
|
|
|
While this post just scratches the surface of Pry, I highly recommend that you dig deeper. Learning more about Pry will significantly reduce headaches and increase efficiency.
|
|
|
|
## Extra Resources
|
|
Below are some extra resources that I've found helpful for learning Pry
|
|
|
|
- [Pry GitHub Page](https://github.com/pry/pry)
|
|
- [Pry Wiki](https://github.com/pry/pry/wiki)
|
|
- [YouTube Talk by Luke Bergen](https://www.youtube.com/watch?v=o90CCPjcIKE)
|
|
|