puts / p (Basic Output)

Ruby's standard output methods. puts outputs strings, p outputs inspect method results of objects. Used as simplest debugging means for temporary log output in learning stages and simple scripts. Inappropriate for full-scale logging.

LoggingRubyDebuggingStandard LibrarySimpleDevelopment Support

Library

puts/p Methods

Overview

The puts/p methods are simple yet powerful debugging and logging techniques built into Ruby as standard. The puts method outputs the to_s representation of objects, while the p method calls the inspect method to display more detailed object information. Especially the p method is an essential debugging tool for Ruby developers, allowing instant inspection of instance variables, class names, and object IDs with comprehensive information during development debugging sessions.

Details

The puts/p methods are the most simple and reliable debugging techniques at the core of Ruby language with over 20 years of proven track record. puts is primarily used for strings and basic output, while the p method is designed to examine the internal state of objects in detail. The p method functions as a shortcut combining inspect method and puts, clearly distinguishing nil values, empty strings, and empty hashes to prevent oversight during debugging. It features IDE integration, no external library requirements, and immediate execution, maximizing development efficiency.

Key Features

  • Built-in Standard Library: Included in Ruby without additional gem installation
  • Detailed Object Display: p method simultaneously shows instance variables and class information
  • Immediate Execution: Instant debugging information without compilation needed
  • nil/Empty Value Distinction: Clearly identifies empty strings, nil, and empty hashes
  • Lightweight Implementation: Extremely small overhead with high-speed operation
  • Framework Independent: Available in any framework like Rails, Sinatra, etc.

Pros and Cons

Pros

  • Built into Ruby as standard, immediately available without external dependencies
  • Efficient debugging with detailed internal object state display through p method
  • Almost zero learning cost, usable from Ruby beginners to advanced developers
  • Extremely small runtime overhead with no performance impact
  • Consistent operation across any development environment regardless of IDE or editor
  • Perfect for quick verification in small implementation cycles during test-driven development

Cons

  • No log level control functionality, requiring manual removal in production
  • Impossible to customize output destination settings or formatting
  • Inappropriate for structured log management in large-scale applications
  • Risk of debug puts/p statements leaking into production in team development
  • No advanced features like log persistence, searching, or analysis
  • Difficult to manage complex debugging sessions across multiple files

Reference Pages

Code Examples

Basic Setup

# No additional installation or require statements needed
# Immediately available if Ruby is installed

# Available at the beginning of Ruby scripts, IRB/Pry, or Rails console
puts "Hello, Ruby Logging!"
p "Hello, Ruby Debugging!"

Basic Log Output and Debugging

# puts method - basic string output
puts "Application started"
puts "Authenticating user..."
puts "Database connection complete"

# Display variable contents with puts
name = "John Doe"
age = 30
puts "Username: #{name}"
puts "Age: #{age}"

# p method - detailed object information display
user_data = { name: "John Doe", age: 30, email: "[email protected]" }
p user_data
# => {:name=>"John Doe", :age=>30, :email=>"john@example.com"}

# Clearly display differences between nil, empty string, empty array
p nil          # => nil
p ""           # => ""
p []           # => []
p {}           # => {}

puts nil       # => (empty line output)
puts ""        # => (empty line output)
puts []        # => (empty line output)

# Detailed display of arrays and hashes
users = [
  { id: 1, name: "John Doe", role: "admin" },
  { id: 2, name: "Jane Smith", role: "user" }
]

puts "User array:"
puts users  # => Each element's to_s representation displayed

puts "\nUser array (detailed):"
p users     # => Complete object structure displayed

# Detailed display including object class information
class User
  attr_accessor :name, :email, :created_at
  
  def initialize(name, email)
    @name = name
    @email = email
    @created_at = Time.now
  end
end

user = User.new("Mike Johnson", "[email protected]")

puts user   # => #<User:0x00007f8b1c0a1d20>
p user      # => #<User:0x00007f8b1c0a1d20 @name="Mike Johnson", @email="mike@example.com", @created_at=2025-01-01 12:00:00 +0900>

Advanced Debugging Techniques

# Debug multiple values simultaneously
def calculate_total(items)
  puts "=== calculate_total debug start ==="
  p "Input items: #{items}"
  
  total = 0
  items.each_with_index do |item, index|
    puts "Processing: #{index + 1}/#{items.length}"
    p "Current item: #{item}"
    
    if item.respond_to?(:price)
      total += item.price
      p "Total after price addition: #{total}"
    else
      puts "Warning: #{item} does not have price information"
      p "Item type: #{item.class}"
      p "Item methods: #{item.methods.sort}"
    end
  end
  
  puts "=== Final result ==="
  p "Total amount: #{total}"
  puts "=== calculate_total debug end ==="
  
  total
end

# Sample data for debugging
Product = Struct.new(:name, :price)
items = [
  Product.new("Laptop", 800),
  Product.new("Mouse", 20),
  "Invalid data"  # Intentionally problematic data
]

result = calculate_total(items)

# Debugging conditional branches
def process_user(user)
  puts "User processing started"
  p "User object: #{user}"
  
  case user[:status]
  when "active"
    puts "Processing active user"
    p "Last login: #{user[:last_login]}"
  when "inactive"
    puts "Processing inactive user"
    p "Inactive reason: #{user[:inactive_reason]}"
  when nil
    puts "Status not set"
    p "Complete user data: #{user}"
  else
    puts "Unknown status"
    p "Received status: #{user[:status]}"
    p "Expected statuses: ['active', 'inactive']"
  end
end

# Debug execution with test data
test_users = [
  { name: "John Doe", status: "active", last_login: Time.now - 3600 },
  { name: "Jane Smith", status: "inactive", inactive_reason: "resigned" },
  { name: "Mike Johnson", status: nil },
  { name: "Bob Wilson", status: "suspended" }
]

test_users.each { |user| process_user(user) }

Error Handling and Debugging

# Debugging in exception handling
def safe_divide(a, b)
  puts "Division operation started"
  p "Dividend: #{a}, Divisor: #{b}"
  
  begin
    result = a / b
    puts "Division successful"
    p "Result: #{result}"
    result
  rescue ZeroDivisionError => e
    puts "Zero division error occurred"
    p "Error message: #{e.message}"
    p "Error class: #{e.class}"
    p "Backtrace: #{e.backtrace.first(3)}"
    nil
  rescue StandardError => e
    puts "Unexpected error occurred"
    p "Error object: #{e}"
    p "Error message: #{e.message}"
    p "Argument types: a=#{a.class}, b=#{b.class}"
    raise  # Re-raise the error
  end
end

# Debug execution examples
test_cases = [
  [10, 2],      # Normal case
  [10, 0],      # Zero division error
  ["10", "2"],  # Strings (potential TypeError)
  [10, nil]     # nil (potential TypeError)
]

test_cases.each do |a, b|
  puts "\n" + "=" * 40
  puts "Test case: #{a} ÷ #{b}"
  result = safe_divide(a, b)
  p "Final result: #{result}"
end

# Debugging nested data structures
def analyze_nested_data(data)
  puts "Nested data analysis started"
  p "Data structure: #{data}"
  
  if data.is_a?(Hash)
    puts "Hash data analysis"
    data.each do |key, value|
      puts "Key: #{key}"
      p "Value: #{value}"
      p "Value class: #{value.class}"
      
      if value.is_a?(Array)
        puts "  Array element analysis:"
        value.each_with_index do |item, index|
          puts "    Index #{index}:"
          p "    Value: #{item}"
          p "    Class: #{item.class}"
        end
      elsif value.is_a?(Hash)
        puts "  Nested hash analysis:"
        value.each do |nested_key, nested_value|
          puts "    Nested key: #{nested_key}"
          p "    Nested value: #{nested_value}"
        end
      end
    end
  else
    puts "Non-hash data"
    p "Data type: #{data.class}"
    p "Data content: #{data}"
  end
end

# Test complex nested data
complex_data = {
  user: {
    id: 1,
    profile: {
      name: "John Doe",
      settings: ["theme:dark", "lang:en"]
    }
  },
  posts: [
    { id: 1, title: "Post 1", tags: ["ruby", "programming"] },
    { id: 2, title: "Post 2", tags: [] }
  ],
  metadata: nil
}

analyze_nested_data(complex_data)

Practical Examples (Rails/Web Application Integration)

# Rails controller debugging example
class UsersController < ApplicationController
  def create
    puts "User creation process started"
    p "Received parameters: #{params}"
    
    @user = User.new(user_params)
    puts "New user object created"
    p "User attributes: #{@user.attributes}"
    p "Pre-validation state: valid=#{@user.valid?}"
    
    if @user.errors.any?
      puts "Validation errors present"
      p "Error details: #{@user.errors.full_messages}"
    end
    
    if @user.save
      puts "User save successful"
      p "Saved user: #{@user}"
      redirect_to @user, notice: 'User was created successfully'
    else
      puts "User save failed"
      p "Error content: #{@user.errors.full_messages}"
      render :new
    end
  end

  private

  def user_params
    permitted = params.require(:user).permit(:name, :email, :age)
    puts "Permitted parameters"
    p permitted
    permitted
  end
end

# Rails model debugging example
class User < ApplicationRecord
  before_save :debug_before_save
  after_save :debug_after_save
  
  validates :name, presence: true, length: { minimum: 2 }
  validates :email, presence: true, uniqueness: true
  
  private
  
  def debug_before_save
    puts "User#before_save callback"
    p "Pre-save attributes: #{attributes}"
    p "Changed attributes: #{changed_attributes}"
  end
  
  def debug_after_save
    puts "User#after_save callback"
    p "Post-save ID: #{id}"
    p "Post-save attributes: #{attributes}"
  end
end

# Background job debugging
class EmailSendJob < ApplicationJob
  queue_as :default

  def perform(user_id, email_type)
    puts "EmailSendJob execution started"
    p "User ID: #{user_id}, Email type: #{email_type}"
    
    user = User.find(user_id)
    puts "User retrieval completed"
    p "User information: #{user}"
    
    case email_type
    when 'welcome'
      puts "Welcome email sending process"
      UserMailer.welcome_email(user).deliver_now
    when 'reminder'
      puts "Reminder email sending process"
      UserMailer.reminder_email(user).deliver_now
    else
      puts "Unknown email type"
      p "Received email type: #{email_type}"
      raise ArgumentError, "Unknown email type: #{email_type}"
    end
    
    puts "EmailSendJob execution completed"
  rescue => e
    puts "Error occurred in EmailSendJob"
    p "Error: #{e}"
    p "Backtrace: #{e.backtrace.first(5)}"
    raise
  end
end