Short answer: Ruby 3.3 and later give you string interpolation with "#{x}", symbols as hash keys ({ host: "db01" }), blocks on every collection (map, select, tally), pattern matching with case/in, one-line methods (def area(w:, h: 1) = w * h), immutable value objects with Data.define, and begin/rescue/ensure for errors. The tables below give the syntax you need most, with notes where Ruby 3.4 behaves differently.
The examples in this Ruby cheat sheet were run with Ruby 3.3.6 on 9 October 2026, with warnings enabled (ruby -w). Output shown as comments is from that run.
Table of Contents
Strings, numbers and symbols
| Syntax | What it does | Example |
|---|---|---|
"…#{x}…" / '…' | Double quotes interpolate; single quotes do not | "Host #{name}:#{port}" |
<<~CONF … CONF | Squiggly heredoc; strips leading indentation | Config templates |
:symbol | Immutable name, used for keys and options | cfg[:host] |
7 / 2, 7.0 / 2, 7.fdiv(2) | Integer division vs float division | 3, 3.5, 3.5 |
10.divmod(3), 2**10 | Quotient and remainder; power | [3, 1], 1024 |
.strip, .upcase, .split(",") | Common string methods | "a,b".split(",") |
.sub / .gsub | Replace first / all matches | "a-b-c".gsub("-", "_") |
.start_with?, .end_with?, .include? | Predicates end in ? | file.end_with?(".conf") |
.length vs .bytesize | Characters vs bytes | "héllo".length is 5, bytesize is 6 |
format("%-8s %5d", h, p) | printf-style formatting | format("%.2f", 0.756) |
"42abc".to_i vs Integer("42") | Lenient vs strict conversion (Integer("x") raises) | Integer(ENV["PORT"]) |
obj&.method | Safe navigation: returns nil if obj is nil | user&.email |
Only nil and false are falsy. 0, "" and [] are all true in a condition. Add # frozen_string_literal: true at the top of a file to freeze string literals; Ruby 3.4 also warns (with deprecation warnings on) when you mutate an unfrozen literal.
Arrays and Enumerable
| Method | What it does | Example |
|---|---|---|
a << x, a.push(x) | Append | hosts << "web02" |
a.first, a.last(2), a[-1] | Access from either end | logs.last(10) |
map, select, reject | Transform, keep, drop | a.select(&:even?) |
find, any?, all?, count | Search and test | a.any? { |n| n > 4 } |
reduce, sum | Fold; sum also takes a block | a.sum { |n| n * 2 } |
sort, sort_by, min_by, max_by(n) | Ordering | files.sort_by { |f| File.mtime(f) } |
group_by, partition, tally | Group, split in two, count occurrences | %w[a b a].tally gives a=2, b=1 |
each_slice(n), each_cons(n) | Batches and sliding windows | ids.each_slice(100) { … } |
each_with_index, zip | Index or pair up | a.each_with_index { |v, i| … } |
uniq, compact, flatten | Remove duplicates / nils / nesting | [1, nil, 2].compact |
(1..5), (1...5) | Inclusive and exclusive ranges | (1..5).step(2).to_a gives [1, 3, 5] |
first, *rest = a | Splat destructuring | head, *tail = lines |
%w[a b c] | Array of words | %w[web01 web02] |
&:even? turns a method name into a block. Numbered block parameters (_1) work in 3.3; Ruby 3.4 adds it for the single parameter, as in a.map { it * 2 }. On 3.3, it without arguments only prints a warning about that coming change.
Hashes
| Syntax | What it does | Example |
|---|---|---|
{ host: "db01", port: 3306 } | Symbol keys (shorthand for :host => …) | cfg[:host] |
h[:missing] vs h.fetch(:k) | [] returns nil; fetch raises KeyError | cfg.fetch(:ttl, 300) with a default |
h.key?(:k) | Key exists | cfg.key?(:host) |
each, map, select | Iterate with |k, v| | cfg.each { |k, v| puts "#{k}=#{v}" } |
merge | New hash; right side wins | defaults.merge(cfg) |
transform_values, to_h | Rebuild values or pairs | cfg.transform_values(&:to_s) |
slice, except | Keep or drop keys | cfg.except(:password) |
dig | Safe nested lookup | cfg.dig(:db, :primary, :host) |
Hash.new(0) | Default value for missing keys | counts[ip] += 1 |
{ x:, y: } | Value shorthand (local variables x and y) | { host:, port: } |
Ruby 3.4 changed Hash#inspect output from {:host=>"db01"} to {host: "db01"}. Tests that compare inspected strings need updating after an upgrade.
Control flow and pattern matching
| Syntax | What it does | Example |
|---|---|---|
if / elsif / else / end | Conditionals; if is an expression | msg = if code >= 500 then "server" else "ok" end |
unless, trailing if | Negated and one-line forms | puts "big" if code > 100 |
case x when … | Matches with === (ranges, classes, regexps) | when 400..499 then "client" |
n.times, 1.upto(3), each | Idiomatic loops | 3.times { |n| print n } |
next, break | Skip or stop inside a block | next if n == 2 |
case x in pattern | Structural pattern matching | see below |
x in pattern / x => pattern | Boolean test / destructure or raise | config => { name: } |
config = { name: "web01", ports: [80, 443], role: "web" }
case config
in { role: "web", ports: [first_port, *] }
puts "web on #{first_port}" # web on 80
in { role: "db" }
puts "db"
end
case 7
in Integer | Float => num if num > 5 # alternatives, binding and a guard
puts "big #{num}"
end
A case/in with no matching branch and no else raises NoMatchingPatternError.
Methods, blocks, procs and lambdas
| Syntax | What it does | Example |
|---|---|---|
def greet(name, greeting = "Hello") | Positional with default | greet("Ann") |
def area(w:, h: 1) | Required and optional keyword arguments | area(h: 2, w: 3) |
*rest, **opts, &blk | Extra positional, extra keywords, explicit block | def run(*args, **opts, &blk) |
def area(w:, h: 1) = w * h | Endless one-line method | def to_s = "#{host}:#{port}" |
yield, block_given? | Call the block passed to a method | yield line if block_given? |
enum_for(:name) | Return an Enumerator when no block is given | each_twice.to_a |
->(n) { n * n } | Lambda: strict arity, return leaves the lambda | sq.(3) or sq.call(3) |
proc { |a, b| … } | Proc: lenient arity, missing args are nil | add.call(1) |
a.map(&sq) | Pass a lambda as a block | [1, 2, 3].map(&sq) |
def valid? / def save! | Convention: ? returns bool, ! is the dangerous version | sort! changes in place |
Classes, modules, Struct and Data
class Server
include Comparable
attr_reader :host, :port
DEFAULT_PORT = 22
def initialize(host, port: DEFAULT_PORT)
@host = host
@port = port
end
def <=>(other) = port <=> other.port
def to_s = "#{host}:#{port}"
def self.local = new("127.0.0.1")
private
def secret = "x"
end
class WebServer < Server
def to_s = "web " + super
end
puts Server.new("db01", port: 3306) # db01:3306
puts WebServer.new("w1") # web w1:22
| Feature | Syntax | Note |
|---|---|---|
| Accessors | attr_reader, attr_writer, attr_accessor | Generate getters and setters |
| Class method | def self.local | Called as Server.local |
| Inheritance | class WebServer < Server | super calls the parent method |
| Mixin | module Greeter … end then include Greeter | Shared methods without inheritance |
| Struct | Point = Struct.new(:x, :y) | Mutable, compares by value |
| Data (3.2+) | Coord = Data.define(:lat, :lng) | Immutable; c.with(lat: 5) returns a copy |
| Introspection | respond_to?, is_a?, ancestors | Server.ancestors.take(3) |
Exceptions
class ConfigError < StandardError; end
begin
raise ConfigError, "missing /etc/app.yml" unless File.exist?("/etc/app.yml")
rescue ConfigError, Errno::ENOENT => e
puts "Error: #{e.message} (#{e.class})"
else
puts "no error"
ensure
puts "cleanup" # always runs
end
| Syntax | What it does |
|---|---|
rescue => e | Catches StandardError and subclasses (not Exception) |
retry | Rerun the begin block; keep a counter to avoid loops |
x = Integer(s) rescue -1 | Inline rescue for a fallback value |
SystemCallError | Parent of Errno::ENOENT, Errno::EACCES and other OS errors |
def m … rescue … end | A method body can have rescue without begin |
Files, JSON and shell commands
| Task | Code |
|---|---|
| Read a whole file | File.read("/etc/hostname") |
| Lines without newlines | File.readlines(path, chomp: true) |
| Stream a large file | File.foreach(path) { |line| … } |
| Append | File.open(path, "a") { |f| f.puts "line" } (the block closes the file) |
| Glob | Dir.glob("/etc/nginx/conf.d/*.conf") |
| Parse JSON | require "json" then JSON.parse(text, symbolize_names: true) |
| Write JSON | File.write(path, JSON.pretty_generate(data)) |
| Run a command safely | require "open3" then out, status = Open3.capture2("systemctl", "is-active", unit) |
| Quick command | out = `uptime`, then $?.success? |
| Environment | ENV.fetch("DB_HOST", "localhost") |
For server admins: Ruby is handy for log summaries. This counts HTTP status codes and the top client IPs in a combined-format access log:
codes = File.foreach("access.log").map { |l| l.split[8] }.tally
p codes.sort_by { |_, n| -n } # [["200", 2], ["404", 1]]
top = File.foreach("access.log").map { |l| l[/\A\S+/] }.tally.max_by(10) { |_, n| n }
Backticks and system("cmd #{x}") with a single string pass through the shell, so a hostname or filename containing ; can run extra commands. Pass arguments separately, as in the Open3.capture2 example.
Official documentation (checked 9 October 2026): Ruby 3.3 documentation · Ruby 3.4 documentation · Ruby pattern matching · ruby-lang.org documentation index · Ruby downloads and current releases
Ruby Cheat Sheet at a glance


Related guides: Python Cheat Sheet: Syntax, Data Types and File Handling · PHP Cheat Sheet: PHP 8.3 and 8.4 Syntax and Functions · Regex Cheat Sheet: PCRE, JavaScript, Python and grep -E.
Reading a lot of our guides? Go ad-free with srvScripts Pro for $6 a month. It also pays for new guides. Cancel any time.