Ruby includes several standard error types: RuntimeError, NoMethodError, NameError, ArgumentError, and others. The full list is in the official docs.
rescue
Use the begin → rescue → end structure to catch errors. The rescue => e form captures the error object into a variable:
class Calc
def sum(a, b)
begin
result = a + b
puts result
rescue => e
puts "Error: #{e.class} - #{e}"
end
end
end
calc = Calc.new
calc.sum("1", 2)
=> Error: TypeError - no implicit conversion of Integer into String
calc.sum(1, "23")
=> Error: TypeError - String can't be coerced into Integer
calc.sum(nil, nil)
=> Error: NoMethodError - undefined method `+' for nil:NilClass
If you don't need to declare variables before the block, you can omit begin — rescue then applies to the entire method body:
class Calc
def sum(a, b)
result = a + b
puts result
rescue => e
puts "Error: #{e.class} - #{e}"
end
end
calc = Calc.new
calc.sum({}, [])
=> Error: NoMethodError - undefined method `+' for {}:Hash
Catching every exception with bare rescue => e is often wrong — you should know exactly what you're handling. Pass the specific error class to rescue:
class Calc
def sum(a, b)
result = a + b
puts result
rescue TypeError
puts "Our TypeError"
rescue NoMethodError => e
puts "Custom NoMethodError: #{e}"
rescue => e
puts "Error: #{e.class} - #{e}"
end
end
calc = Calc.new
calc.sum(nil, nil)
=> Custom NoMethodError: undefined method `+' for nil:NilClass
calc.sum(1, "2")
=> Our TypeError
To see the exact line where the error occurred, call e.backtrace:
class Calc
def div(a, b)
result = a / b
puts result
rescue => e
puts e.backtrace
end
end
calc = Calc.new
calc.div(10, 0)
=>
calc.rb:3:in `/'
calc.rb:3:in `div'
calc.rb:12:in `<main>'
You can list multiple error types separated by a comma to handle them together:
class Calc
def div(a, b)
puts a / b
rescue ZeroDivisionError, TypeError, NoMethodError
puts "Something was wrong"
rescue => e
puts "Error: #{e.class} - #{e}"
end
end
calc = Calc.new
calc.div(10, 0)
=> Something was wrong
calc.div("123", 1)
=> Something was wrong
raise
The raise keyword lets you throw any error, including a plain string message:
require 'json'
class Parser
def parse(data)
raise 'no_data' unless data
JSON.parse(data)
rescue JSON::ParserError
puts "Wrong json"
rescue => e
puts "Other Error: #{e}"
end
end
pr = Parser.new
pr.parse(nil)
=> Other Error: no_data
pr.parse("{a: 1}")
=> Wrong json
Custom Errors
Create your own error class by inheriting from StandardError:
class MyCustomError < StandardError
def initialize(msg = 'Default error')
super(msg)
end
end
class Calc
def sum(a, b)
raise MyCustomError.new('My custom error here') if b < 0
puts a + b
rescue MyCustomError => e
puts e
end
def sum2(a, b)
raise MyCustomError.new if b < 0
puts a + b
rescue MyCustomError => e
puts e
end
end
calc = Calc.new
calc.sum(2, -3)
=> My custom error here
calc.sum2(2, -3)
=> Default error
retry
retry restarts the begin block from the top. Always use a counter to prevent an infinite loop:
class Sender
def call
retry_count = 0
begin
send_mail!
puts 'sended'
rescue => e
if retry_count <= 3
retry_count += 1
puts 'retrying'
retry
end
puts "Error: #{e}"
end
end
def send_mail!
puts "sending mail..."
raise 'send error' if rand(10) < 5
end
end
Sender.new.call
=>
sending mail...
sended
Sender.new.call
=>
sending mail...
retrying
sending mail...
retrying
sending mail...
retrying
sending mail...
sended
ensure
ensure runs its block regardless of whether an error was raised — ideal for cleanup and logging:
class Sender
def call
retry_count = 0
begin
send_mail!
puts 'sended'
rescue => e
if retry_count <= 3
retry_count += 1
puts 'retrying'
retry
end
puts "Error: #{e}"
ensure
puts "Log event"
end
end
def send_mail!
puts "sending mail..."
raise 'send error' if rand(10) < 5
end
end
Sender.new.call
=>
sending mail...
sended
Log event
Sender.new.call
=>
sending mail...
retrying
sending mail...
sended
Log event
Namespaces
Modules let you organize code when multiple classes share the same name. For example, a Pay class exists for both TBC and BOG banks — they live in separate folders:
tbc/pay.rb
bog/pay.rb
# tbc/pay.rb
module TBC
URL = 'tbcbank.ge'
class Pay
def call
puts "TBC pay - #{URL}"
end
end
end
# bog/pay.rb
module BOG
URL = 'bog.ge'
class Pay
def call
puts "BOG pay - #{URL}"
end
end
end
TBC::Pay.new.call
=> TBC pay - tbcbank.ge
BOG::Pay.new.call
=> BOG pay - bog.ge
Monkey Patching
In Ruby you can redefine any method by reopening an existing class:
name = 'Alex'
puts name.upcase
=> ALEX
class String
def upcase
self.split('').join('_')
end
end
puts name.upcase
=> A_l_e_x
You can also extend existing types with entirely new methods:
puts 28.to_age
=> NoMethodError: undefined method `to_age' for 28:Integer
class Integer
def to_age
"Age: #{self}"
end
end
puts 28.to_age
=> Age: 28
Monkey Patching causes serious maintenance problems: if a standard method is silently overridden somewhere in the codebase, the program can break in unexpected ways that are very hard to debug.
Refinements
Ruby 2.0 introduced Refinements as a safer alternative — they scope modifications to only the classes where you explicitly activate them. Declare a module and use refine to describe what you want to override:
module MyString
refine String do
def upcase
self.split('').join('_')
end
end
refine Integer do
def to_building
"Building: #{self}"
end
end
end
Activate the refinement in the desired class with using:
class CustomClassA
using MyString
def address(name, num)
puts "Street: #{name.upcase}, #{num.to_building}"
end
end
class CustomClassB
def address(name, num)
num = num.respond_to?(:to_building) ? num.to_building : num
puts "Street: #{name.upcase}, #{num}"
end
end
CustomClassA.new.address('Tsereteli', 123)
=> Street: T_s_e_r_e_t_e_l_i, Building: 123
CustomClassB.new.address('Tsereteli', 123)
=> Street: TSERETELI, 123
using also works inside a module:
module Money
refine Integer do
def to_d
"$#{self}"
end
end
end
module BankMethods
using Money
def deposit_money(sum)
puts "Your deposit: " + sum.to_d
end
end
class Bank
include BankMethods
end
Bank.new.deposit_money(100)
=> Your deposit: $100