# Debug Ruby Applications with pry

Debugging Ruby applications becomes significantly easier when you have tools that provide insight into your code's execution. While Ruby ships with a basic debugger, developers often reach for more powerful alternatives when troubleshooting complex issues.

[pry](https://github.com/pry/pry) is a runtime developer console for Ruby that **replaces the default IRB (Interactive Ruby) shell**. It offers syntax highlighting, robust introspection capabilities, and a plugin ecosystem that extends its functionality well beyond basic debugging.

With pry, you can pause execution at any point in your code, **inspect objects and variables**, navigate the call stack, and even modify running code to test fixes immediately.

This article will walk you through using pry to debug Ruby applications effectively.

[ad-logs]

## Prerequisites

Before you begin, make sure you have:

- Ruby 3.3+ installed on your machine
- Familiarity with basic Ruby syntax and application structure

## Setting up the project directory

You'll create a clean workspace for practicing debugging techniques with pry.

Start by creating a directory for the debugging examples and navigate into it:

```command
mkdir ruby_debugging && cd ruby_debugging
```

Ruby's bundler manages gems on a per-project basis, so initialize a new bundle:

```command
bundle init
```

This creates a `Gemfile` where you can specify project dependencies. Open the `Gemfile` and add pry:

```ruby
[label Gemfile]
source 'https://rubygems.org'

[highlight]
gem 'pry'
[/highlight]
```

Install the gem:

```command
bundle install
```

The gem is now available for your debugging session, isolated from other Ruby projects on your system.

## Debugging Ruby scripts with pry from the command line

The pry gem transforms Ruby's debugging experience by providing an enhanced REPL (Read-Eval-Print Loop) with features like command history, syntax highlighting, and powerful introspection tools. You can launch pry directly to debug scripts without modifying your code.

Create a Ruby script that calculates prime numbers. Save this as `prime_checker.rb`:

```ruby
[label prime_checker.rb]
def prime?(number)
  return false if number < 2
  return true if number == 2
  
  (2..Math.sqrt(number).to_i).each do |divisor|
    return false if number % divisor == 0
  end
  
  true
end

def primes_up_to(limit)
  (2..limit).select { |n| prime?(n) }
end

puts primes_up_to(50).inspect
```

Running this normally produces a list of prime numbers up to 50:

```command
ruby prime_checker.rb
```

```text
[output]
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47]
```

To debug this script with pry, you can load it directly into a pry session:

```command
pry -r ./prime_checker.rb
```

```text
[output]
[1] pry(main)>
```

When pry launches this way:

1. Your script executes completely before the prompt appears
2. All methods and constants from your script remain available in the session
3. The pry prompt `[1] pry(main)>` indicates you're in the main context
4. You can call any methods defined in your script interactively

From here, you can interact with your code using pry's command set:

- `show-source` or `$`: Display the source code of a method
- `ls`: List methods and variables available in the current scope
- `cd`: Navigate into an object's context
- `whereami`: Show your current location in the code
- `exit`: Leave the pry session
- `help`: Display available commands

These commands let you explore your code's structure interactively. Try examining the `prime?` method and then press **ENTER**:

```text
[1] pry(main)> show-source prime?
```

```text
[output]
From: /Users/stanley/ruby_debugging/prime_checker.rb:1:
Owner: Object
Visibility: private
Signature: prime?(number)
Number of lines: 10

def prime?(number)
  return false if number < 2
  return true if number == 2
  
  (2..Math.sqrt(number).to_i).each do |divisor|
    return false if number % divisor == 0
  end
  
  true
end
```

You can test methods directly with different inputs:

```text
[2] pry(main)> prime?(17)
```

```text
[output]
=> true
```

```text
[3] pry(main)> prime?(18)
```

```text
[output]
=> false
```

The `ls` command reveals what's available in your current scope:

```text
[4] pry(main)> ls
```

```text
[output]
self.methods: inspect  to_s
locals: _  __  _dir_  _ex_  _file_  _in_  _out_  pry_instance
```

To see detailed information about a method, including where it's defined:

```text
[5] pry(main)> show-source primes_up_to
```

```text
[output]
...
Number of lines: 3

def primes_up_to(limit)
  (2..limit).select { |n| prime?(n) }
end
```

One of pry's strengths is its ability to navigate into object contexts. You can examine what methods a number responds to:

```text
[6] pry(main)> cd 42
[7] pry(42):1> ls
```

This displays all methods available on the integer object. Press `q` to exit the list view, then return to the main context:

```text
[8] pry(42):1> cd ..
[9] pry(main)>
```

You can also evaluate Ruby expressions directly to test logic:

```text
[10] pry(main)> (2..10).select { |n| n.even? }
```

```text
[output]
=> [2, 4, 6, 8, 10]
```

This interactive exploration helps you understand how your code behaves without repeatedly editing and running the script.


## Embedding pry breakpoints in your code

Rather than loading your entire script into pry, you can pause execution at specific points by inserting breakpoints directly in your code. This approach works better when:

- You've identified a specific area where bugs likely exist
- You want to inspect variable states at precise moments during execution
- Your application has complex initialization that makes command-line debugging impractical
- You're debugging code that's triggered by external events or user input

Here's how to add a breakpoint to your Ruby code:

```ruby
[label prime_checker.rb]
[highlight]
require 'pry'

[/highlight]
def prime?(number)
  return false if number < 2
  return true if number == 2
  
[highlight]
  binding.pry  # Execution pauses here
  
[/highlight]
  (2..Math.sqrt(number).to_i).each do |divisor|
    return false if number % divisor == 0
  end
  
  true
end

def primes_up_to(limit)
  (2..limit).select { |n| prime?(n) }
end

puts primes_up_to(50).inspect
```

When you run this script:

```command
ruby prime_checker.rb
```

Execution stops at the `binding.pry` line, dropping you into an interactive session:

```text
[output]
From: /Users/stanley/prime_checker.rb:7 Object#prime?:

     3: def prime?(number)
     4:   return false if number < 2
     5:   return true if number == 2
     6:   
 =>  7:   binding.pry  # Execution pauses here
     8: 
     9:   (2..Math.sqrt(number).to_i).each do |divisor|
    10:     return false if number % divisor == 0
    11:   end

[1] pry(main)>
```

Several things happen at this breakpoint:

1. Pry displays the surrounding code with line numbers
2. An arrow `=>` marks the current line where execution is paused
3. You have access to all local variables in the current scope
4. You can inspect method parameters and modify values to test different scenarios

Check the current value of the `number` parameter:

```text
[1] pry(main)> number
```

```text
[output]
=> 3
```

You can see all local variables in scope:

```text
[2] pry(main)> ls -l
```

```text
[output]
number = 3
```

To continue execution until the next breakpoint or the program ends, use the `exit` command:

```text
[3] pry(main)> exit
```

The program will hit the breakpoint again for the next number being checked. You can see the updated value:

```text
[output]
From: /Users/stanley/prime_checker.rb:7 Object#prime?:

     3: def prime?(number)
     4:   return false if number < 2
     5:   return true if number == 2
     6:   
 =>  7:   binding.pry
     8: 
     9:   (2..Math.sqrt(number).to_i).each do |divisor|
    10:     return false if number % divisor == 0
    11:   end
```

```text
[1] pry(main)> number
=> 4
```

Type `exit-program` to stop execution entirely and exit the script.

### Using Ruby's built-in debug gem

Ruby 3.1 introduced a built-in debug gem that works similarly to pry. If you prefer not to add external dependencies, you can use the `debugger` keyword:

```ruby
[highlight]
# No require statement needed
[/highlight]

def prime?(number)
  return false if number < 2
  return true if number == 2
  
[highlight]
  debugger  # Uses Ruby's built-in debugger
[/highlight]
  
  (2..Math.sqrt(number).to_i).each do |divisor|
    return false if number % divisor == 0
  end
  
  true
end
```

This approach has trade-offs:

- Zero setup: No gems to install or require statements needed
- Different interface: The commands differ from pry's syntax
- Less powerful: Fewer introspection and navigation features
- Standard across environments: Available wherever Ruby 3.1+ runs

For this guide, we'll continue using pry since it offers more debugging capabilities and a better interactive experience.

## Navigating execution with step commands

When debugging complex logic, you often need to trace execution step by step. Standard pry doesn't include step commands, but the `pry-byebug` gem adds this functionality.

Install `pry-byebug`:

```command
gem install pry-byebug
```

Create a new file that demonstrates nested method calls:

```ruby
[label calculator.rb]
require 'pry-byebug'

def multiply(a, b)
  result = a * b
  result
end

def calculate_area(length, width)
  binding.pry
  area = multiply(length, width)
  area
end

puts "Area: #{calculate_area(5, 10)}"
```

Run the script:

```command
ruby calculator.rb
```

When execution pauses at the breakpoint, pry-byebug automatically positions you at the next executable line:

```text
[output]
From: /Users/stanley/calculator.rb:10 Object#calculate_area:

     8: def calculate_area(length, width)
     9:   binding.pry
 => 10:   area = multiply(length, width)
    11:   area
    12: end

[1] pry(main)>
```

The `step` command lets you step into method calls:

```text
[1] pry(main)> step
```

```text
[output]
From: /Users/stanley/calculator.rb:4 Object#multiply:

    3: def multiply(a, b)
 => 4:   result = a * b
    5:   result
    6: end

[1] pry(main)>
```

You're now inside the `multiply` method. Check the parameter values:

```text
[1] pry(main)> a
```

```text
[output]
=> 5
```

```text
[2] pry(main)> b
```

```text
[output]
=> 10
```

The `next` command executes the current line and moves to the next one within the same method:

```text
[3] pry(main)> next
```

```text
[output]
From: /Users/stanley/calculator.rb:5 Object#multiply:

    3: def multiply(a, b)
    4:   result = a * b
 => 5:   result
    6: end

[3] pry(main)>
```

Check the result that was just calculated:

```text
[4] pry(main)> result
```

```text
[output]
=> 50
```

The `finish` command completes the current method and returns to the caller:

```text
[5] pry(main)> finish
```

```text
[output]
From: /Users/stanley/calculator.rb:11 Object#calculate_area:

     8: def calculate_area(length, width)
     9:   binding.pry
    10:   area = multiply(length, width)
 => 11:   area
    12: end

[5] pry(main)>
```

You're back in `calculate_area`, and the `area` variable now holds the return value from `multiply`:

```text
[6] pry(main)> area
```

```text
[output]
=> 50
```

These navigation commands give you precise control over execution flow, letting you trace exactly how data moves through your program.

## Inspecting state with stack navigation

When debugging method chains or nested calls, understanding the call stack becomes critical. Pry's stack navigation commands let you move up and down the call stack to inspect variables at different execution levels.

Create a script that demonstrates a deeper call chain:

```ruby
[label stack_example.rb]
require 'pry-byebug'

def level_three
  message = "Deep in the stack"
  binding.pry
  message
end

def level_two
  data = [1, 2, 3]
  level_three
end

def level_one
  count = 10
  level_two
end

level_one
```

Run the script:

```command
ruby stack_example.rb
```

Execution pauses inside `level_three`:

```text
[output]
From: /Users/stanley/stack_example.rb:6 Object#level_three:

    4: def level_three
    5:   message = "Deep in the stack"
 => 6:   binding.pry
    7:   message
    8: end

[1] pry(main)>
```

The `whereami` command shows your current position with more context:

```text
[1] pry(main)> whereami
```

```text
[output]
From: /Users/stanley/stack_example.rb:6 Object#level_three:

    1: require 'pry-byebug'
    2: 
    3: def level_three
    4:   message = "Deep in the stack"
    5:   binding.pry
 => 6:   message
    7: end
```

You can access the local `message` variable:

```text
[2] pry(main)> message
```

```text
[output]
=> "Deep in the stack"
```

The `up` command moves one level up the call stack:

```text
[3] pry(main)> up
```

```text
[output]
Frame number: 1/4

From: /Users/stanley/stack_example.rb:11 Object#level_two:

    10: def level_two
    11:   data = [1, 2, 3]
 => 12:   level_three
    13: end
```

You're now in `level_two`'s context. You can access its local variables:

```text
[4] pry(main)> data
```

```text
[output]
=> [1, 2, 3]
```

Move up another level:

```text
[5] pry(main)> up
```

```text
[output]
Frame number: 2/4

From: /Users/stanley/stack_example.rb:17 Object#level_one:

    15: def level_one
    16:   count = 10
 => 17:   level_two
    18: end
```

Now you can see variables from `level_one`:

```text
[6] pry(main)> count
```

```text
[output]
=> 10
```

The `down` command moves back down the stack:

```text
[7] pry(main)> down
```

```text
[output]
Frame number: 1/4

From: /Users/stanley/stack_example.rb:12 Object#level_two:

    10: def level_two
    11:   data = [1, 2, 3]
 => 12:   level_three
    13: end
```

You're back in `level_two`. The `backtrace` command shows the complete call stack:

```text
[8] pry(main)> backtrace
```

```text
[output]
--> #0  Object#level_two at /Users/stanley/stack_example.rb:12
    #1  Object#level_one at /Users/stanley/stack_example.rb:17
    #2  <main> at /Users/stanley/stack_example.rb:20
```

This navigation ability proves valuable when debugging complex applications where understanding the sequence of method calls helps identify where things go wrong.



## Final thoughts
As you’ve seen throughout this guide, Pry brings a new level of control and visibility to debugging Ruby applications. Instead of relying on simple print statements or Ruby’s basic debugger, Pry **allows you to pause execution, inspect objects, and explore your code interactively**.

By integrating tools like `pry` and `pry-byebug`, you can step through execution, navigate the call stack, and test fixes in real time. This hands-on approach not only speeds up troubleshooting but also deepens your understanding of how your Ruby code actually runs.

In the end, adopting Pry transforms debugging from a tedious task into a powerful, exploratory process that helps you write cleaner, more reliable Ruby applications.
