Emergency server help: get in touch

Ruby Cheat Sheet: Syntax, Blocks, Hashes and Classes

A compact Ruby 3.3+ reference with tables for strings, collections, blocks, pattern matching, classes, exceptions and file handling.

Published 7 min read

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.

Strings, numbers and symbols

SyntaxWhat it doesExample
"…#{x}…" / '…'Double quotes interpolate; single quotes do not"Host #{name}:#{port}"
<<~CONF … CONFSquiggly heredoc; strips leading indentationConfig templates
:symbolImmutable name, used for keys and optionscfg[:host]
7 / 2, 7.0 / 2, 7.fdiv(2)Integer division vs float division3, 3.5, 3.5
10.divmod(3), 2**10Quotient and remainder; power[3, 1], 1024
.strip, .upcase, .split(",")Common string methods"a,b".split(",")
.sub / .gsubReplace first / all matches"a-b-c".gsub("-", "_")
.start_with?, .end_with?, .include?Predicates end in ?file.end_with?(".conf")
.length vs .bytesizeCharacters vs bytes"héllo".length is 5, bytesize is 6
format("%-8s %5d", h, p)printf-style formattingformat("%.2f", 0.756)
"42abc".to_i vs Integer("42")Lenient vs strict conversion (Integer("x") raises)Integer(ENV["PORT"])
obj&.methodSafe navigation: returns nil if obj is niluser&.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

MethodWhat it doesExample
a << x, a.push(x)Appendhosts << "web02"
a.first, a.last(2), a[-1]Access from either endlogs.last(10)
map, select, rejectTransform, keep, dropa.select(&:even?)
find, any?, all?, countSearch and testa.any? { |n| n > 4 }
reduce, sumFold; sum also takes a blocka.sum { |n| n * 2 }
sort, sort_by, min_by, max_by(n)Orderingfiles.sort_by { |f| File.mtime(f) }
group_by, partition, tallyGroup, split in two, count occurrences%w[a b a].tally gives a=2, b=1
each_slice(n), each_cons(n)Batches and sliding windowsids.each_slice(100) { … }
each_with_index, zipIndex or pair upa.each_with_index { |v, i| … }
uniq, compact, flattenRemove 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 = aSplat destructuringhead, *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

SyntaxWhat it doesExample
{ host: "db01", port: 3306 }Symbol keys (shorthand for :host => …)cfg[:host]
h[:missing] vs h.fetch(:k)[] returns nil; fetch raises KeyErrorcfg.fetch(:ttl, 300) with a default
h.key?(:k)Key existscfg.key?(:host)
each, map, selectIterate with |k, v|cfg.each { |k, v| puts "#{k}=#{v}" }
mergeNew hash; right side winsdefaults.merge(cfg)
transform_values, to_hRebuild values or pairscfg.transform_values(&:to_s)
slice, exceptKeep or drop keyscfg.except(:password)
digSafe nested lookupcfg.dig(:db, :primary, :host)
Hash.new(0)Default value for missing keyscounts[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

SyntaxWhat it doesExample
if / elsif / else / endConditionals; if is an expressionmsg = if code >= 500 then "server" else "ok" end
unless, trailing ifNegated and one-line formsputs "big" if code > 100
case x when …Matches with === (ranges, classes, regexps)when 400..499 then "client"
n.times, 1.upto(3), eachIdiomatic loops3.times { |n| print n }
next, breakSkip or stop inside a blocknext if n == 2
case x in patternStructural pattern matchingsee below
x in pattern / x => patternBoolean test / destructure or raiseconfig => { 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

SyntaxWhat it doesExample
def greet(name, greeting = "Hello")Positional with defaultgreet("Ann")
def area(w:, h: 1)Required and optional keyword argumentsarea(h: 2, w: 3)
*rest, **opts, &blkExtra positional, extra keywords, explicit blockdef run(*args, **opts, &blk)
def area(w:, h: 1) = w * hEndless one-line methoddef to_s = "#{host}:#{port}"
yield, block_given?Call the block passed to a methodyield line if block_given?
enum_for(:name)Return an Enumerator when no block is giveneach_twice.to_a
->(n) { n * n }Lambda: strict arity, return leaves the lambdasq.(3) or sq.call(3)
proc { |a, b| … }Proc: lenient arity, missing args are niladd.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 versionsort! 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
FeatureSyntaxNote
Accessorsattr_reader, attr_writer, attr_accessorGenerate getters and setters
Class methoddef self.localCalled as Server.local
Inheritanceclass WebServer < Serversuper calls the parent method
Mixinmodule Greeter … end then include GreeterShared methods without inheritance
StructPoint = Struct.new(:x, :y)Mutable, compares by value
Data (3.2+)Coord = Data.define(:lat, :lng)Immutable; c.with(lat: 5) returns a copy
Introspectionrespond_to?, is_a?, ancestorsServer.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
SyntaxWhat it does
rescue => eCatches StandardError and subclasses (not Exception)
retryRerun the begin block; keep a counter to avoid loops
x = Integer(s) rescue -1Inline rescue for a fallback value
SystemCallErrorParent of Errno::ENOENT, Errno::EACCES and other OS errors
def m … rescue … endA method body can have rescue without begin

Files, JSON and shell commands

TaskCode
Read a whole fileFile.read("/etc/hostname")
Lines without newlinesFile.readlines(path, chomp: true)
Stream a large fileFile.foreach(path) { |line| … }
AppendFile.open(path, "a") { |f| f.puts "line" } (the block closes the file)
GlobDir.glob("/etc/nginx/conf.d/*.conf")
Parse JSONrequire "json" then JSON.parse(text, symbolize_names: true)
Write JSONFile.write(path, JSON.pretty_generate(data))
Run a command safelyrequire "open3" then out, status = Open3.capture2("systemctl", "is-active", unit)
Quick commandout = `uptime`, then $?.success?
EnvironmentENV.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

Ruby Cheat Sheet – short answer
In short: A compact Ruby 3.3+ reference with tables for strings, collections, blocks, pattern matching, classes, exceptions and file handling.
Ruby Cheat Sheet – what this guide covers
Ruby Cheat Sheet: what this guide covers.

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.

Share this guide

Free website test

Is your website set up right?

Check SSL, security headers, redirects, robots.txt, sitemap, llms.txt and security.txt in one test. It takes about 30 seconds.