Ruby Cheatsheet - Ruby Syntax & Methods Reference

This reference is for developers picking up Ruby (including Rails) or recalling its idioms. It starts with variables, symbols, and common built-in methods, then arrays/hashes, control flow (including postfix if and unless), methods and blocks/Proc/Lambda, classes & modules with attr_accessor, include/extend, and inheritance, and ends with begin-rescue-ensure exception handling. Unlike many languages, almost everything in Ruby is an object and methods return the value of their last expression, and blocks elegantly wrap iteration. After reading you should be able to write idiomatic, concise Ruby: iterate with blocks instead of hand loops, use symbols as hash keys, and organize reusable behavior into modules.

Languages·69 commands·Last updated 2026-07-21
rubyrailsRuby on RailsProgramming Languages

Typical Use Case

For developers writing Ruby scripts, reshaping data or maintaining Rails code. The confusion concentrates in a few places: the real difference between a symbol and a string, the three ways blocks travel and are invoked (yield, Proc.new, lambda), the reader and writer methods attr_accessor generates, and the collection chain from map through select, reject, partition and group_by. Most real tasks are one expression — filter, transform, group, reduce — and this sheet gives you the vocabulary for it. Classes and mixins, exception handling, and catch with throw cover the longer-term organisation needs.

Basics 7

name = "Ruby"
Variable assignment, dynamic typing needs no declaration
name = "Hello, #{name}"
String interpolation, works in double quotes
:symbol
Symbol, immutable and unique, good as hash key
# single-line comment
Single-line comment starts with #
=begin comment block =end
Multi-line comment block
nil
Nil value, nil is also an object
true / false
Boolean value, also an object

Array & Hash 9

arr = [1, 2, 3]
Create an array
arr << 4
Append an element
arr[0]
Access element at index 0
arr.first / arr.last
First / last element
hash = { a: 1, b: 2 }
Create a hash (symbol-key shorthand)
hash[:a]
Access a hash value by key
hash[:c] = 3
Set a hash value by key
hash.key?(:a)
Check if a key exists
hash.keys / hash.values
Get all keys / values

Control Flow 10

if x > 0 puts "positive" end
if conditional
puts "positive" if x > 0
Trailing if, concise form
unless x > 0 puts "not positive" end
unless conditional, equivalent to if !
case x when 1 then "one" else "other" end
case multi-branch match
3.times { puts "hi" }
Repeat a fixed number of times
1.upto(5) { |n| puts n }
Increment from 1 to 5
5.downto(1) { |n| puts n }
Decrement from 5 to 1
loop { break if done }
Infinite loop, needs manual break
while x > 0 x -= 1 end
while loop
until x == 0 x -= 1 end
until loop, equivalent to while !

Methods 9

def greet(name) "Hello, #{name}" end
Define a method, last line auto-returns
def greet(name = "World")
Method parameter with default value
def sum(*args)
splat operator, accepts variable args
def method(a:, b:)
Keyword arguments, order-independent
return value
Explicit return value (optional)
def method yield if block_given? end
yield invokes the passed block
my_proc = Proc.new { |x| x*2 }
Create a Proc object
my_lambda = ->(x) { x*2 }
Create a Lambda (arrow syntax)
proc.call(5)
Call a Proc / Lambda

Class & Module 10

class MyClass end
Define a class
attr_accessor :name
Auto-generate getter and setter
attr_reader :name
Read-only getter
attr_writer :name
Write-only setter
class Child < Parent
Class inheritance
module MyModule end
Define a module
include MyModule
Mixin module methods (instance methods)
extend MyModule
Mixin module methods (class methods)
self.class_method
self refers to current object, define class method
class << self end
Singleton class, define class methods

Exception 7

begin risky_call rescue "error" end
begin-rescue catches exceptions
rescue SpecificError => e
Catch a specific exception type
ensure cleanup end
ensure guarantees cleanup runs
raise "error message"
Manually raise an exception
raise ArgumentError, "msg"
Raise a specific exception type
retry
retry inside rescue repeats the begin block
catch(:done) { throw(:done) }
catch/throw non-local jump

Built-in Methods 10

arr.map { |x| x*2 }
Array#map transforms an array
arr.select { |x| x.even? }
Array#select filters evens
arr.reject { |x| x.even? }
Array#reject excludes evens
arr.reduce(0) { |s, x| s + x }
Array#reduce sums
hash.merge({ c: 3 })
Hash#merge merges hashes
"hello".upcase
String#upcase uppercases
"hello".split("")
String#split splits a string
"hello".gsub(/l/, "L")
String#gsub global replace
arr.flatten
Array#flatten flattens nested arrays
arr.uniq
Array#uniq deduplicates

Blocks & Iterators 7

[1, 2].each { |n| puts n }
each iterates over elements
[1, 2].each do |n| puts n end
do...end block, equivalent to {}
(1..5).map { |n| n**2 }
map transforms (returns new array)
[1, 2, 3].select(&:even?)
select with &: shorthand
arr.partition { |x| x.even? }
partition into two groups [[evens], [odds]]
arr.chunk { |x| x > 0 }
chunk groups by condition
arr.group_by { |x| x % 3 }
group_by groups into a hash by condition

Parameter matrix

ParameterEffectExample
name = "Ruby"Assigns to a dynamically typed local; no type declaration is involved.name = "Ruby"
"Hello, #{name}"Interpolates a variable inside a double-quoted string.puts "Hello, #{name}"
:symbolA symbol is immutable and unique, which makes it a good hash key.hash = { a: 1, b: 2 }
arr << 4Appends an element to the end of an array.arr = [1, 2, 3]; arr << 4
arr.first / arr.lastTakes the first element / the last element.arr.first
hash.key?(:a)Tests whether a key is present.hash.key?(:a)
hash.keys / hash.valuesReturns all keys / all values.hash.keys
puts "positive" if x > 0Postfix if, the modifier form of a conditional.puts "positive" if x > 0
unless x > 0 ... endRuns the body when the condition does not hold, equivalent to a negated if.unless x > 0; puts "not positive"; end
case x when 1 then ... else ... endMatches one value against several branches.case x; when 1 then "one"; else "other"; end
3.times { ... }Repeats the block a fixed number of times.3.times { puts "hi" }
def greet(name = "World")Defines a method with a default argument value.def greet(name = "World"); "Hello, #{name}"; end
def sum(*args)A splat parameter accepts a variable number of arguments.def sum(*args); args.sum; end
def method(a:, b:)Keyword arguments, which may be supplied in any order.def config(a:, b:); end
def method; yield if block_given?; endCalls the block that the caller passed in.def each_x; yield if block_given?; end
Proc.new { |x| x*2 } / ->(x) { x*2 }Creates a Proc object, or a lambda using the arrow syntax.my_lambda = ->(x) { x * 2 }; my_lambda.call(5)
attr_accessor :nameGenerates a reader and a writer in one declaration.attr_accessor :name
include MyModule / extend MyModuleMixes instance methods in / mixes class methods in.include MyModule
arr.map { |x| x*2 }Transforms the array and returns a new one.arr.map { |x| x * 2 }
arr.select / arr.rejectKeeps the elements that pass the test / drops the elements that do.arr.select { |x| x.even? }
arr.reduce(0) { |s, x| s + x }Folds the array into a single accumulated value.arr.reduce(0) { |s, x| s + x }
arr.group_by { |x| x % 3 }Groups elements by a computed key and returns a hash.arr.group_by { |x| x % 3 }
arr.partition { |x| x.even? }Splits the array into the matching and the non-matching group in one pass.arr.partition { |x| x.even? }
rescue SpecificError => eCatches a specific exception class and binds it to a variable.begin; risky_call; rescue SpecificError => e; end
ensure ... endRuns cleanup code whether the body succeeded or raised.begin; risky_call; ensure; cleanup; end
raise ArgumentError, "msg"Raises an exception of a given class.raise ArgumentError, "msg"
catch(:done) { throw(:done) }A non-local jump, used to bail out of deeply nested loops.catch(:done) { throw(:done) }

Common pitfalls

SymptomLooking a hash up with a string key returns nil even though the value was just written.

CauseA symbol and a string are different objects; hash lookups match on type and equality, and keys are never coerced.

FixSettle on one convention, normally symbol keys such as hash[:a], or convert explicitly at the read site with hash[sym.to_s] or hash[key.to_sym].

Symptommap was used to change an array in place, yet the original array is untouched and later code reads stale data.

Causemap, select and reject all return new arrays and leave the receiver alone; only forms such as << or []= mutate it.

FixCapture the return value, for example arr = arr.map { |x| x * 2 }, or switch to the bang variants such as select! and map!.

SymptomAfter a break inside a loop, a large block of outer code is skipped and the execution order looks random.

CauseA break inside a block terminates the enclosing loop of the whole method call rather than only the block it appears in.

FixUse next to skip just one iteration, and reach for a catch(:done) with throw(:done) pair when the jump has to escape several nesting levels.

SymptomA method with no explicit return hands back an intermediate value left over from inside a block.

CauseA Ruby method returns the value of its last expression, and assignments or traversals inside a block change what that last expression turns out to be.

FixPut the intended value on the final line of the method, or write return explicitly.

SymptomA method defined with yield fails when called plainly, with a LocalJumpError.

Causeyield only has a block to run when the caller supplies one; without a block there is simply nothing to execute.

FixGuard with block_given? before yielding, or add a block at the call site using { ... } or do ... end.

SymptomA begin and rescue pair fails to catch anything and the exception still reaches the top level.

CauseA rescue clause only protects the begin it is attached to; placing it at a different nesting level creates a separate scope and the exception escapes.

FixWrap the risky call entirely in a begin ... rescue => e ... ensure ... end structure and check that the indentation levels line up.

SymptomAdding a batch of methods to a class silently overwrote a same-named instance method.

CauseMethods from an included module enter the ancestor chain above the class itself, so they win over same-named definitions in the class.

FixDraw the boundary explicitly: general capabilities belong in modules included with include, logic that serves only this class stays defined in the class, and class-level methods are mixed in separately with extend.

SymptomA long single-line block glues itself to the neighbouring call and breakpoints land in the wrong place.

CauseCurly braces bind tightly, which makes them easy to confuse with surrounding code inside a long expression, and do...end follows different binding rules in several situations.

FixUse do ... end for multi-line blocks and reserve { } for single-line ones, and let editor quick fixes such as Alt+Enter help locate the boundary.

Troubleshooting

  1. 1Confirm the key type and contents of a hash

    hash.keys; hash.key?(:a); hash[:a]; hash["a"]

    The keys output shows whether keys are symbols or strings. Probe the same data once with :a and once with "a": only the matching type returns a value and the other returns nil, which pins the mismatch down immediately.

  2. 2Establish what an array actually holds

    arr = [1, 2, 3]; arr.map { |x| x * 2 }; arr; arr.select { |x| x.even? }; arr.uniq

    map returns a new array and leaves arr alone, select returns a filtered new array, and uniq removes duplicates. To confirm whether a change took effect, inspect arr itself rather than an intermediate result in a chain.

  3. 3Tell a single split apart from repeated filtering

    arr = [1,2,3,4,5]; even, odd = arr.partition { |x| x.even? }; even; odd

    partition walks the array once and returns two arrays that multiple assignment unpacks directly. If the result looks wrong, the predicate no longer matches the element type, for instance because the elements are strings.

  4. 4Group and count by a condition

    arr.group_by { |x| x % 3 }; arr.group_by { |x| x % 3 }.map { |k, v| [k, v.size] }

    group_by returns a hash from key to the array of matching elements, and taking the size of each value yields the per-group counts, which is a quick check that the grouping logic behaves.

  5. 5Verify the block really ran

    def each_x; yield if block_given?; "done"; end; each_x { |v| puts v }

    No output at all means the block_given? check failed or no block was passed; a returned "done" means the method body ran to the end, which distinguishes "the block did not run" from "the method was never called".

  6. 6Fold a sum while staying safe on an empty array

    arr.reduce(0) { |s, x| s + x }; [].reduce(0) { |s, x| s + x }

    Passing an initial 0 lets an empty array return 0; dropping it and calling reduce with only a block raises on an empty array, so always pass the seed explicitly in scripts.

Command Examples

Chain enumerables so the data cleanup reads top to bottom

arr = [1, 2, 3, 4, 5, 6] arr.select(&:even?).map { |x| x * 10 }.reduce(0) { |s, x| s + x }

select, map and reduce each return new collections and leave the original array untouched, so chaining them is safe. &:even? is shorthand that converts a method name into the symbol the block calls, removing a |x| boilerplate.

Output

=> 120

Group data by condition to see the distribution

arr = [1, 2, 3, 4, 5] arr.group_by { |x| x % 3 } arr.partition { |x| x > 3 }

group_by returns a hash, which suits per-bucket statistics; partition returns two arrays where the first element is the passing group — the order is fixed but easy to remember backwards.

Output

{0=>[3], 1=>[1, 4], 2=>[2, 5]}
[[4, 5], [1, 2, 3]]

String substitution and splitting

log = "2024-05-01 ERROR timeout" log.gsub(/\d{4}-\d{2}-\d{2}/, "DATE") log.split(" ")

gsub needs a regex to match patterns like a date; a plain string only does literal replacement. gsub returns a new string and never mutates the original, so there is no destructive_ variant to reach for.

Output

=> "DATE ERROR timeout"
=> ["2024-05-01", "ERROR", "timeout"]

Keyword arguments make call sites self-documenting

def connect(host:, port: 443, timeout: 30) puts "#{host}:#{port} in #{timeout}s" end connect(host: "db.internal")

Keyword arguments are order-independent, and omitting one raises ArgumentError instead of silently landing in the wrong slot — which is exactly why connect("a", "b") style mistakes get caught at the boundary.

Output

=> db.internal:443 in 30s

Class skeleton with attr_accessor

class User attr_accessor :name def initialize(name) @name = name end end User.new("Tom").name

attr_accessor generates a reader and a writer in one line; attr_reader makes the attribute read-only from outside. The instance variable @name must be assigned in initialize or it reads as nil without any error.

Output

=> "Tom"

Common Pitfalls

  • { } binds tighter than do...end, so array.each { puts method } passes method into the block as an argument and raises ArgumentError; use do...end there.
  • Array indexing starts at 0, and arr[-1] is the last element — the off-by-one that follows from this contaminates every slice built on it.
  • The last expression in a method body becomes the return value, so inserting a puts or a log line mid-method silently changes the result, often to nil.
  • A bare rescue catches far more than you intend and can swallow Interrupt, making a script impossible to stop with Ctrl+C.
  • proc and lambda check arity differently: lambda demands an exact argument count, so treating one as the other removes a guard rail you may rely on.
  • Ruby strings are mutable, so str << "x" edits in place. If the string is a hash key or a constant, mutating it corrupts the key and produces key not found.

Tips

  • Everything in Ruby is an object, including nil and numbers.
  • each is the most common iterator; map transforms, select filters.
  • Symbol :symbol is immutable and unique, good as a hash key.
  • attr_accessor auto-generates getter and setter.
  • Blocks are Ruby's most powerful feature; do...end and {} are equivalent.

FAQ

Should I reach for map, select or reject — do they differ in return value?

Exactly: map transforms element by element and returns a new array of the same length; select keeps only the elements for which the block is true, so the length changes; reject keeps the ones for which it is false and pairs naturally with select — arr.partition { |x| x.even? } hands you both groups at once as [[evens], [odds]]. Use each when you only want the side effects, since its return value is the original array and map would just waste a copy.

When should a hash key be a symbol rather than a string?

Symbols are immutable and interned — there is exactly one instance per program run — which makes them fast hash keys and the right choice for method names like attr_accessor :name. Strings are mutable and each literal evaluation may allocate anew, so a string key costs extra content hashing. Rule of thumb: symbols for identifiers you write, strings for concatenated values, user input, or anything that has to match parsed JSON. Note that hash.key?(:a) and hash.key?("a") never match each other.

How do Proc and Lambda differ, and what goes wrong if I pick the wrong one?

Both are built from block syntax — Proc.new { |x| x*2 } and ->(x) { x*2 } — but they differ in how strictly they are called. Lambda is strict: the argument count must match its definition, and return exits the method where the lambda was defined. Proc is lenient about arity, and a return inside it raises LocalJumpError. When a callback is invoked by code you do not control, or when you depend on return semantics, the arrow syntax is the safer choice.

How do I generate accessors for a class, and why not write both methods by hand?

attr_accessor :name generates both the reader and the writer in one line; attr_reader :name gives you only the getter, which is what you want when the field should stay read-only and you do not want callers to bypass validation; attr_writer :name gives only the setter. Hand-writing paired getters and setters is verbose and easy to half-update during a rename. Declared in the class body these become instance methods — to make a module's methods class methods use extend MyModule, since include only mixes in instance methods.

How do I exit early from a loop — break, next, or catch/throw?

break leaves the innermost loop or 3.times { } block immediately; next is the equivalent of continue and just skips to the next iteration. Both only affect one level, so neither helps when you are inside nested loops and need to terminate the outer one. That is what the non-local jump is for: catch(:done) { throw(:done) }, usually combined with break or return, lets a generator-style routine bail out from any depth inside a single block.

Official References

Each command links to its official documentation below, so you can verify the latest usage and read deeper.

Maintained by LaoHand

Publicly updated on Jul 21, 2026, continuously proofread against official docs.

Contact Us

Wrong command or description? Send us corrections, business inquiries or product feedback by email.

Contact Us