iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Exceptions

Ruby exceptions are raised and rescued. Build a hierarchy by subclassing StandardError; rescue specific classes, not Exception. ensure for cleanup; retry within rescue.

Raise, rescue, custom, retry, ensure

EXAMPLE
# 1) Define your own hierarchy — subclass StandardError
module Payment
    class Error          < StandardError; end
    class CardDeclined   < Error; end
    class GatewayDown    < Error; end
    class InvalidAmount  < Error; end
end

# 2) Raise
raise Payment::CardDeclined, "Card xxx-1111 declined"
# Or with attributes
class Payment::CardDeclined < Payment::Error
    attr_reader :code
    def initialize(message, code:)
        super(message)
        @code = code
    end
end

raise Payment::CardDeclined.new("declined", code: 'INSUFFICIENT_FUNDS')

# 3) Rescue — specific first, generic last
begin
    process_payment(amount: 100, card: card)
rescue Payment::CardDeclined => e
    log.warn("card declined: \#{e.code} #{e.message}")
    notify_user(e.message)
rescue Payment::GatewayDown
    schedule_retry
rescue Payment::Error => e
    log.error(e)
    raise               # re-raise unknown payment errors
ensure
    close_connection    # runs even if no exception
end

# 4) NEVER `rescue Exception`
# Exception is the parent of SystemExit, Interrupt (Ctrl+C), SignalException.
# Default `rescue` (no class) catches StandardError — the right behaviour.

# BAD
# begin work; rescue Exception => e; ...; end

# GOOD
begin
    work
rescue => e         # StandardError + its subclasses
    log.error(e)
end

# 5) Retry — for transient failures
attempts = 0
begin
    attempts += 1
    api.call
rescue Net::OpenTimeout, Net::ReadTimeout => e
    retry if attempts < 3
    raise
end

# 6) Inline rescue (return on failure) — handy but use sparingly
result = fetch_from_cache rescue nil

# 7) Method-level rescue
def create_user(params)
    User.create!(params)
rescue ActiveRecord::RecordInvalid => e
    { error: e.record.errors.full_messages }
end

# 8) Raising with .new vs raising the class
raise Payment::Error                       # default message = class name
raise Payment::Error, "declined"           # message via 2nd arg
raise Payment::Error.new("declined")       # equivalent
raise Payment::CardDeclined.new("declined", code: 'X')   # custom attrs

# 9) Exception object info
rescue => e
    e.class           # the exception class
    e.message         # the message string
    e.backtrace       # array of "file:line:in `method'" frames
    e.cause           # the original exception, if one was wrapping another (Ruby 2.1+)
end

# 10) Chain exceptions — explicit cause
begin
    do_low_level_work
rescue StandardError => low
    raise HighLevelError, "failed at top level"
    # The new exception's `cause` is automatically the low-level one.
end

# 11) Common idioms

# Try/return — if all OK, return; on error, return a tuple
def safe_parse(s)
    [JSON.parse(s), nil]
rescue JSON::ParserError => e
    [nil, e]
end

# Wrap untrusted code with a clean error
def with_retries(times: 3)
    attempts = 0
    begin
        attempts += 1
        yield
    rescue StandardError => e
        retry if attempts < times
        raise
    end
end

with_retries { http.get(url) }

# 12) Logging — backtrace is gold
rescue => e
    log.error("\#{e.class}: \#{e.message}")
    log.error(e.backtrace.first(20).join(\"\\n\"))
end

# 13) Test exceptions
# RSpec
expect { do_thing }.to raise_error(Payment::CardDeclined, /declined/)
expect { do_thing }.to raise_error(Payment::CardDeclined) { |e| expect(e.code).to eq('X') }

# 14) When to raise vs return a result
# - Raise for EXCEPTIONAL conditions (DB down, invalid state, programmer error)
# - Return a value (or Result/Either) for expected outcomes (validation, lookup miss)

Why it matters

Build a small exception hierarchy under StandardError and rescue specifically. Bare rescue => e catches StandardError (good); rescue Exception catches SIGINT and breaks Ctrl+C (bad).

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
begin
    raise 'boom' if bad?
rescue StandardError => e
    puts "caught: #{e.message}"
ensure
    cleanup
end
Try it Yourself »

Discussion

Loading…