CrackedRuby logo

CrackedRuby

Pry

Overview

Pry replaces Ruby's standard IRB REPL with an enhanced interactive environment that provides runtime code inspection, object exploration, and debugging capabilities. Ruby loads Pry as a gem that intercepts method calls and provides immediate access to the current binding context.

The core Pry system operates through the Pry class and associated command processors. When invoked, Pry captures the current execution context, including local variables, method definitions, and object state. The REPL accepts Ruby expressions and executes them within this captured binding.

require 'pry'

def calculate_total(items)
  binding.pry  # Execution pauses here
  items.sum { |item| item[:price] * item[:quantity] }
end

calculate_total([{price: 10, quantity: 2}, {price: 5, quantity: 3}])

Pry implements a command system that extends beyond Ruby evaluation. Commands prefixed with periods execute Pry-specific functionality for code navigation, documentation access, and debugging operations. The Pry::Command class hierarchy defines these built-in commands.

The binding inspection mechanism works through Ruby's Binding class, capturing the lexical scope at the point where Pry starts. This includes access to local variables, instance variables, method definitions, and constants available in that scope.

class User
  def initialize(name)
    @name = name
    binding.pry  # Access to @name and local context
  end
end

Basic Usage

Starting a Pry session requires calling binding.pry within Ruby code or launching pry as a standalone REPL. The binding.pry call creates a breakpoint that halts execution and opens an interactive session with access to the current scope.

require 'pry'

def process_order(order_data)
  validated_data = validate_order(order_data)
  binding.pry  # Inspect validated_data and order_data
  create_order(validated_data)
end

The Pry prompt displays the current context, showing the class or module scope and nesting level. Commands execute by typing them at the prompt. Basic navigation uses cd to change context between objects and ls to list available methods and variables.

# In Pry session
cd User              # Navigate into User class
ls                   # List methods and constants
cd @current_user     # Navigate into instance
ls -i                # List instance variables

Code display commands show source definitions and documentation. The show-method command reveals method implementations, while show-doc displays method documentation. These commands accept method names, including those with special characters.

# Display method source
show-method User#authenticate
show-method User.find_by_email

# Show documentation  
show-doc Array#map
show-doc String#gsub

Variable inspection works through direct evaluation and specialized commands. Typing a variable name evaluates and displays its value. The whereami command shows the current execution location with surrounding code context.

def complex_calculation(data)
  intermediate_result = data.map { |x| x * 2 }
  binding.pry
  # At this point:
  # data               # Shows original data
  # intermediate_result # Shows mapped data
  # whereami           # Shows current line with context
end

Session control uses exit to leave the current Pry session and continue execution. The exit-all command terminates all nested Pry sessions. The next and step commands provide basic debugging navigation when using pry-byebug.

History access through the hist command shows previously executed commands. The edit command opens methods or classes in the configured editor. Shell commands execute by prefixing with periods or using the shell-mode command.

Advanced Usage

Pry configuration customizes behavior through the Pry.config object and ~/.pryrc configuration file. The configuration system accepts options for prompt display, editor selection, command aliases, and output formatting.

# ~/.pryrc configuration
Pry.config.editor = 'vim'
Pry.config.auto_indent = true
Pry.config.correct_indent = true
Pry.config.collision_warning = true

# Custom prompt
Pry.config.prompt = Pry::Prompt.new(
  :custom,
  'Custom prompt',
  [
    proc { |obj, nest_level, _| "#{obj}:#{nest_level}> " },
    proc { |obj, nest_level, _| "#{obj}:#{nest_level}* " }
  ]
)

Custom command creation extends Pry functionality through the Pry::Command base class. Commands define their own parsing logic, help text, and execution behavior. The command registration system makes these commands available in all Pry sessions.

Pry::Commands.create_command 'find-routes' do
  description 'Find Rails routes matching a pattern'
  
  def process
    pattern = args.first
    Rails.application.routes.routes.select do |route|
      route.path.spec.to_s.include?(pattern)
    end.each do |route|
      output.puts "#{route.verb} #{route.path.spec}"
    end
  end
end

Plugin integration happens through Pry's hook system and gem loading. Popular plugins like pry-byebug add debugging capabilities, while pry-rails integrates with Rails applications. Plugins register through the Pry.plugins registry.

# Gemfile integration
gem 'pry-byebug'
gem 'pry-rails'
gem 'pry-remote'

# Plugin activation in .pryrc
begin
  require 'pry-byebug'
  Pry.commands.alias_command 'c', 'continue'
  Pry.commands.alias_command 's', 'step'
rescue LoadError
  puts "pry-byebug not available"
end

Binding manipulation allows navigation between different execution contexts. The cd command changes the current object context, while nesting shows the current navigation stack. Complex object inspection uses these navigation capabilities.

class ComplexService
  def initialize(config)
    @config = config
    @processors = create_processors
    binding.pry
  end
  
  private
  
  def create_processors
    # In Pry: cd @config, ls, cd .., cd @processors[0]
    []
  end
end

Output customization controls how Pry displays evaluation results. The Pry.config.print option accepts custom print functions that format output. Color schemes and syntax highlighting customize through the coderay configuration.

# Custom output formatting
Pry.config.print = proc do |output, value, _pry_|
  output.puts "=> #{value.inspect}"
  output.puts "   (#{value.class})"
end

# Syntax highlighting
Pry.config.color = true
Pry.config.theme = :solarized

Error Handling & Debugging

Exception handling in Pry sessions captures errors during evaluation and provides detailed stack traces. When expressions raise exceptions, Pry displays the error message, backtrace, and maintains the current binding context for inspection.

def risky_operation(data)
  binding.pry
  # Type: data.unknown_method
  # Pry shows: NoMethodError and maintains context
  # Variables remain accessible for inspection
end

The wtf? command provides detailed exception information for the last raised error. This includes full backtraces with source locations and variable states at each frame. The command accepts numeric arguments to show different levels of detail.

# After an exception occurs
wtf?      # Basic exception info
wtf? 5    # Show 5 stack frames
wtf?!     # Extended debugging information

Debugging integration with pry-byebug adds step-through debugging capabilities. The step, next, continue, and finish commands control execution flow. Breakpoint management uses break to set breakpoints and info breakpoints to list them.

require 'pry-byebug'

def complex_algorithm(input)
  binding.pry
  # Available commands:
  # step    - Step into method calls
  # next    - Move to next line  
  # finish  - Execute until return
  # break User#authenticate - Set breakpoint
end

Variable inspection during exceptions reveals the state at error points. Local variables, instance variables, and method parameters remain accessible through the Pry session. The caller method shows the call stack without stopping execution.

def process_payment(amount, card)
  begin
    charge_card(amount, card)
  rescue PaymentError => e
    binding.pry  # Inspect amount, card, e
    # All variables available for debugging
  end
end

Remote debugging capabilities through pry-remote enable debugging of running processes. The binding.remote_pry call starts a remote Pry server that accepts connections from separate terminals.

require 'pry-remote'

def background_worker
  loop do
    binding.remote_pry  # Connect via: pry-remote
    process_job
  end
end

Stack frame navigation allows movement between different levels of the call stack. The up and down commands move between frames, while frame shows the current frame information. Each frame provides access to its local variable context.

def level_three
  binding.pry
  # up    - Move to level_two's frame
  # down  - Move back to level_three's frame  
  # frame - Show current frame details
end

def level_two
  level_three
end

def level_one
  level_two
end

Testing Strategies

Test environment integration embeds Pry sessions within test suites for debugging failing tests. The binding.pry placement in test methods provides access to test doubles, fixture data, and assertion contexts during test execution.

RSpec.describe UserService do
  let(:user_data) { { name: 'John', email: 'john@example.com' } }
  
  it 'creates user with valid data' do
    service = UserService.new
    binding.pry  # Access to service, user_data, and test context
    result = service.create_user(user_data)
    expect(result).to be_successful
  end
end

Mock and stub inspection during tests reveals the interaction between test doubles and code under test. Pry sessions provide access to mock expectations, method call counts, and received arguments.

describe PaymentProcessor do
  it 'processes payment correctly' do
    gateway = double('PaymentGateway')
    allow(gateway).to receive(:charge).and_return(success: true)
    
    processor = PaymentProcessor.new(gateway)
    binding.pry  # Inspect gateway mock setup
    processor.process_payment(100)
    
    # In Pry: gateway.received_messages shows interactions
  end
end

Fixture data examination uses Pry to inspect database records, factory-generated objects, and test data setup. This reveals the actual state of test fixtures versus expected states.

feature 'User registration' do
  let!(:existing_user) { create(:user, email: 'taken@example.com') }
  
  scenario 'prevents duplicate email registration' do
    visit new_user_path
    binding.pry  # Examine existing_user state and page content
    fill_in 'Email', with: 'taken@example.com'
    click_button 'Register'
    expect(page).to have_content('Email already taken')
  end
end

Conditional debugging activates Pry sessions only when specific test conditions occur. Environment variables or test metadata control when debugging sessions start, preventing interruption of normal test runs.

RSpec.describe ComplexCalculation do
  it 'handles edge cases', :debug do
    result = ComplexCalculation.perform(edge_case_data)
    binding.pry if ENV['DEBUG'] || example.metadata[:debug]
    expect(result).to meet_expectations
  end
end

# Run with debugging: DEBUG=1 rspec spec/calculation_spec.rb
# Or tag-based: rspec --tag debug spec/calculation_spec.rb

Test helper integration provides debugging utilities available across test suites. Custom commands and shortcuts enhance the debugging experience within test contexts.

# spec/spec_helper.rb
RSpec.configure do |config|
  config.before(:each) do |example|
    if example.metadata[:debug]
      Pry.config.input = $stdin
      Pry.config.output = $stdout  
    end
  end
end

# Custom debugging helper
def debug_test(label = nil)
  puts "\n=== #{label} ===" if label
  binding.pry
end

Production Patterns

Production debugging requires careful Pry integration that avoids disrupting normal application flow. Conditional breakpoints use environment variables or feature flags to enable debugging sessions only when explicitly activated.

class OrderProcessor
  def process_order(order)
    # Conditional debugging for production investigation
    binding.pry if ENV['DEBUG_ORDER_PROCESSING'] == 'true'
    
    validate_order(order)
    calculate_totals(order)
    charge_payment(order)
  end
end

Remote debugging in production environments uses pry-remote for investigating running processes without stopping the entire application. The remote connection isolates debugging sessions from normal request processing.

require 'pry-remote' if Rails.env.production? && ENV['ENABLE_REMOTE_DEBUGGING']

class BackgroundJob
  def perform
    binding.remote_pry if should_debug?
    execute_job_logic
  rescue => e
    binding.remote_pry if critical_error?(e)
    raise
  end
  
  private
  
  def should_debug?
    ENV['DEBUG_JOBS'] && job_id_matches_debug_pattern?
  end
end

Rails integration through pry-rails enhances console sessions with Rails-specific functionality. The gem adds Rails route inspection, model introspection, and application configuration access within Pry sessions.

# In Rails console with pry-rails
rails console

# Enhanced Rails functionality
show-routes                    # Display all routes
show-routes users             # Filter routes by pattern  
show-models                   # List all models
reload!                       # Reload application code

Logging integration captures Pry session activity for audit trails and debugging history. Custom logging configurations record commands executed, variables inspected, and session duration.

# Custom Pry logging configuration
Pry.config.hooks.add_hook(:before_session, :logging) do |output, binding, pry|
  Rails.logger.info("Pry session started in #{binding.eval('self.class')}")
end

Pry.config.hooks.add_hook(:after_session, :logging) do |output, binding, pry|
  Rails.logger.info("Pry session ended")
end

Memory and performance monitoring during Pry sessions prevents resource exhaustion in production environments. Memory usage tracking and session timeouts protect against excessive resource consumption.

class ProductionSafePry
  def self.safe_pry(binding_context)
    return unless debugging_enabled?
    
    Timeout::timeout(300) do  # 5-minute session limit
      binding_context.pry
    end
  rescue Timeout::Error
    Rails.logger.warn("Pry session timeout - automatically terminated")
  end
  
  private
  
  def self.debugging_enabled?
    ENV['PRODUCTION_DEBUG'] == 'true' && 
    current_memory_usage < memory_threshold
  end
end

Security considerations protect sensitive data during production debugging. Data sanitization and access controls prevent exposure of credentials, personal information, and sensitive application state.

class SecurePrySession
  def self.sanitize_binding(binding_context)
    # Remove sensitive variables before Pry session
    eval("remove_instance_variable(:@password)", binding_context) rescue nil
    eval("remove_instance_variable(:@api_key)", binding_context) rescue nil
    
    binding_context.pry
  end
end

Reference

Core Commands

Command Parameters Returns Description
cd <object> Object reference nil Navigate into object context
ls [options] -i, -m, -c, -g flags Array List methods, variables, constants
whereami [lines] Number of context lines nil Show current code location
edit <method> Method name or class nil Open method/class in editor
show-method <method> Method name, -l for line numbers nil Display method source code
show-doc <method> Method name nil Display method documentation
hist [options] --head, --tail, --grep nil Show command history
wtf? [depth] Number of frames to show nil Display exception information
exit None nil Exit current Pry session
exit-all None nil Exit all nested Pry sessions

Configuration Options

Option Type Default Description
Pry.config.editor String ENV['EDITOR'] Editor command for code editing
Pry.config.auto_indent Boolean true Automatic indentation
Pry.config.color Boolean true Syntax highlighting
Pry.config.pager Boolean true Paginate long output
Pry.config.collision_warning Boolean true Warn about command conflicts
Pry.config.correct_indent Boolean true Correct indentation mistakes
Pry.config.prompt Pry::Prompt Default prompt Custom prompt configuration

Debugging Commands (pry-byebug)

Command Parameters Returns Description
step [lines] Number of steps nil Step into method calls
next [lines] Number of lines nil Move to next line
finish None nil Execute until method return
continue None nil Continue execution
break <location> Method or line reference nil Set breakpoint
info breakpoints None Array List all breakpoints
delete <number> Breakpoint number nil Delete specific breakpoint
up [frames] Number of frames nil Move up call stack
down [frames] Number of frames nil Move down call stack
frame None nil Show current frame information

Hook Types

Hook Trigger Parameters Description
:before_session Session start output, binding, pry Execute before Pry starts
:after_session Session end output, binding, pry Execute after Pry exits
:when_started First session binding, pry Execute on first Pry invocation
:before_eval Before evaluation code, pry Execute before code evaluation
:after_eval After evaluation result, pry Execute after code evaluation

Common Error Types

Error Cause Resolution
LoadError Missing gem dependencies Install required gems
NoMethodError Invalid command or method Check command spelling and availability
SyntaxError Invalid Ruby syntax Correct syntax errors
Pry::CommandError Invalid command usage Check command help with help <command>
SystemStackError Infinite recursion Avoid recursive Pry calls

Plugin Integration

Plugin Gem Key Features
pry-byebug gem 'pry-byebug' Step debugging, breakpoints
pry-rails gem 'pry-rails' Rails console enhancement
pry-remote gem 'pry-remote' Remote debugging sessions
pry-doc gem 'pry-doc' Core Ruby documentation
pry-theme gem 'pry-theme' Color theme customization