# Exceptions and Error Handling — Ruby

Source: https://www.geekswithgeeks.com/en/ruby/m-errors

> Raise, rescue, retry and ensure, and design custom exception classes.

## Handling failure gracefully

Raise exceptions with **`raise`**: `raise ArgumentError, "amount must be positive"`. Handle them with **`begin ... rescue ... else ... ensure ... end`**, or directly in a method body: `rescue` clauses match exception classes (most specific first), `else` runs when nothing was raised, and **`ensure`** always runs, for cleanup. **`retry`** re-runs the `begin` block, useful with a counter for transient network errors. Rescue **`StandardError`** (what a bare `rescue` catches) or specific subclasses, never **`Exception`**, which also includes system signals and `SystemExit`, making programs hard to stop. Define **custom exceptions** by subclassing `StandardError`, adding attributes for context, and group them under a base class for your library (`PaymentError < StandardError`). Methods with **`!`** commonly raise on failure while their plain versions return `nil` or `false` (`save!` versus `save` in Rails). Use `ensure` or block-based APIs (`File.open` with a block) so resources are always released. Inline `rescue` modifiers (`value = risky rescue nil`) hide all errors and should be avoided.

## Custom errors, retry and ensure

Specific rescues, limited retries and guaranteed cleanup.

```ruby
class PaymentError < StandardError; end

class PaymentDeclined < PaymentError
  attr_reader :reason
  def initialize(reason)
    @reason = reason
    super("Payment declined: #{reason}")
  end
end

class GatewayTimeout < PaymentError; end

def charge(amount, token)
  attempts = 0
  begin
    attempts += 1
    gateway_call(amount, token)
  rescue GatewayTimeout
    retry if attempts < 3                         # transient: try again
    raise
  rescue PaymentDeclined => e
    puts "Declined (#{e.reason}); not retrying"
    nil
  else
    puts "Charged on attempt #{attempts}"
  ensure
    puts "payment attempt finished"               # always runs
  end
end

def gateway_call(amount, token)
  raise ArgumentError, "amount must be positive" unless amount.positive?
  raise PaymentDeclined, "card blocked" if token.start_with?("blocked")
  "pay_123"
end
```

## A safety net with a cleanup crew

`rescue` is the safety net that catches specific falls, `retry` lets the performer try the jump again a limited number of times, and `ensure` is the cleanup crew that sweeps the stage whatever happened.

**Quiz:** Why should you rescue StandardError rather than Exception?

- [ ] Exception does not exist
- [x] Exception also catches signals and SystemExit, making programs hard to interrupt or exit
- [ ] StandardError is faster
- [ ] Ruby forbids rescuing Exception

*Answer:* Exception also catches signals and SystemExit, making programs hard to interrupt or exit. Rescuing Exception swallows interrupts and exit requests, not just application errors.
