DS DevShelfHub Projects · AI tools
Cheatsheets / Ruby
Cheatsheet · Languages

Ruby: Blocks, Enumerable, Mixins and Pattern Matching Reference Guide

By DevShelfHub

Strings, arrays, hashes, blocks, procs, lambdas, classes, modules, mixins, enumerable, pattern matching, exceptions — modern Ruby 3.x surface.

119 items 8 min Blocks Enumerable Mixins

Start hereQuick start · 6 you’ll reach for daily

Blockxs.each { |x| … }
Map & selectxs.map(&:upcase).select(&:any?)
Hashh = { name: "Ada", age: 36 }
Heredoc<<~SQL … SQL
Pattern matchcase x in [a, b, *] => …
Interp"x = #{x}"

Target versions · paceVersions

Targets: ruby ≥ 3.3 bundler ≥ 2.5 YJIT enabled RBS / Sorbet (optional)

Ruby 3.x lineage brings pattern matching, fibers / scheduler-based concurrency, and YJIT (just-in-time compiler — enable with RUBY_YJIT_ENABLE=1 or --yjit). Type information is opt-in via RBS signatures or Sorbet. Modern web stacks: Rails 7 + Hotwire for full-stack, Sinatra / Roda for micro. Background work runs on Sidekiq + async-gem fibers.

Install · bundlerSetup

bash
# Install via rbenv (recommended) or asdf
brew install rbenv ruby-build
rbenv install 3.3.0
rbenv global 3.3.0

# Verify
ruby -v                                # ruby 3.3.0 ...
gem -v && bundler -v

# New project
mkdir myapp && cd myapp
bundle init                            # creates Gemfile
bundle add sinatra puma                # add deps
bundle install                         # install + write Gemfile.lock

# Daily commands
ruby script.rb                         # run a file
bundle exec rspec                      # run tests in the bundle's context
irb -r ./lib/myapp                     # REPL with your code loaded
bundle exec rubocop -A                 # auto-fix lints
gem install bundler                    # update bundler itself

Where things liveStandard library map

require "json"JSON.parse, JSON.generate.
require "csv"RFC-4180 CSV reader / writer.
require "net/http"Built-in HTTP client. Verbose; most prod uses a gem.
require "uri"Parse / build URIs.
require "set"Set class.
require "ostruct"Quick attribute-bag struct.
require "fileutils"rm_rf, mkdir_p, cp, mv.
require "open3"Run shell commands with stdin / stdout / stderr / status.
require "logger"Stdlib structured-ish logger.
require "securerandom"UUIDs, tokens, random bytes.
require "date" / "time"Date arithmetic + extended Time parsing.
require_relative "./foo"Load a sibling file. Skips load-path resolution.

Bindings · control flowSyntax basics

x = 5Local variable. Snake_case.
@instance / @@class / $globalSigils distinguish scope.
MAX = 100Constant. UpperCamelCase. Mutable but conventionally not.
unless cond / until condNegative forms. unless avoids if !.
x += 1 if x > 0Statement modifier — the trailing-if idiom.
a, b = 1, 2Parallel assignment.
a, *rest = [1, 2, 3, 4]Splat destructuring.
case x; when 1..10 then … endCase/when. Uses ===.
case x in {name:, age: 18..} then … endPattern matching (Ruby 3+).
begin … rescue => e … ensure … endException handling.
def name(*args, **kw, &blk)Splat args, double-splat kwargs, block.
def square(x) = x * xEndless method (Ruby 3+).

Text manipulationStrings & symbols

"hello, #{name}"Double-quoted = interpolation + escapes.
'no interp'Single-quoted = literal.
<<~SQL … SQLIndented heredoc. Strips common leading whitespace.
s.upcase / downcase / capitalize / swapcaseCommon case ops. !-versions mutate.
s.strip / chomp / chopTrim whitespace / trailing newline / last char.
s.split(",") / s.split(/\s+/)String or regex separator.
s.gsub(/\d+/, "X")Global substitute. sub = first only.
s.match?(/^\d+$/) / s =~ /pat/ / s.scan(/\w+/)Test / first match / all matches.
s.tr("aeiou", "*")Char-by-char translate.
format("%5.2f %s", pi, name) / "%s: %d" % [k, v]printf-style formatting.
:nameSymbol. Interned immutable identifier.
"hello".to_sym / :hello.to_sConvert string ↔ symbol.

Array · Hash · Range · SetCollections

Arrays

xs = [1, 2, 3] / Array.new(3) { |i| i * 2 }Literal / builder.
%w[a b c] / %i[a b c]Array of strings / symbols.
xs << 4 / xs.push(4) / xs.unshift(0)Append / push end / push front.
xs.first / last / first(3) / last(3)Edge access.
xs.compact / xs.flatten / xs.uniqDrop nils / flatten / dedupe.
xs.zip(ys, zs)Pair up rows across arrays.
xs.partition { |x| x > 0 }Split into matching / non-matching.
xs.each_slice(2) / xs.each_cons(2)Non-overlap chunks / sliding window.

Hashes

h = { name: "Ada", age: 36 }Symbol-key shorthand.
h[:name] / h.fetch(:name, "anon")Index / index-with-default-or-block.
h.merge(other) / h.merge!(other) { |k, a, b| … }Combine with conflict resolution.
h.transform_values { |v| v.upcase }Map over values only.
h.group_by { |k, v| v.class }Bucket entries.
h.slice(:a, :b) / h.except(:a)Sub-hashes.
h.dig(:a, :b, :c)Safe deep access. Returns nil if any link missing.
h.to_a / h.keys / h.valuesConvert / iterate.

Range & Set

(1..10) / (1...10)Inclusive / exclusive range.
(1..).step(2).first(5)Endless ranges + lazy ops.
Set[1, 2, 3] (require "set")Hash-backed set.
(a & b), (a | b), (a - b), (a ^ b)Set operators — on arrays too.

The killer featureBlocks, procs, lambdas

method { |x| … } / method do |x|; … endCurly = inline; do/end = multi-line.
yieldInvoke the implicit block from inside a method.
def f(&blk); blk.call(…); endCapture block as a Proc.
block_given?Check whether a block was passed.
Proc.new { |x| … }Proc. Loose arity. return exits enclosing method.
->(x, y) { x + y }Lambda. Strict arity. return exits the lambda only.
fn.call(x) / fn.(x) / fn[x]Three equivalent invocations.
xs.map(&:upcase)Symbol-to-proc. Same as xs.map { |s| s.upcase }.
method(:foo).to_proc / xs.each(&method(:puts))Pass a method as a block.

Worked example

ruby
# Blocks are the heart of Ruby — every method can take one

# yield invokes the block; block_given? checks
def each_word(text)
  text.split.each { |w| yield w if block_given? }
end

each_word("hello world from ruby") { |w| puts w.upcase }

# Convert block to a Proc with &
def benchmark(&blk)
  t0 = Time.now
  blk.call
  puts "elapsed: #{(Time.now - t0).round(3)}s"
end

benchmark { (1..1_000_000).inject(:+) }

# Procs vs lambdas — lambdas check arity and return locally
square = ->(x) { x * x }
puts square.call(5)        # 25
puts square.(5)            # alt call syntax
puts square[5]             # alt call syntax

# Symbol-to-proc — pass a method by name
%w[hello world].map(&:upcase).each(&method(:puts))

# Yielding with multiple values
def each_pair(h)
  h.each { |k, v| yield k, v }
end
each_pair(a: 1, b: 2) { |k, v| puts "#{k} → #{v}" }

map · select · reduceEnumerable

xs.each / each_with_index / each_with_object({})Iterate. each_with_object threads state.
xs.map { |x| x * 2 } / xs.collectTransform. collect is alias.
xs.flat_map { |x| [x, x] }Map + concat.
xs.select { … } / reject { … }Keep / drop matches.
xs.find { … } / detectFirst match.
xs.count { … }Count matching.
xs.reduce(:+) / inject(0) { |s, x| s + x }Fold. Pass an operator symbol or a block.
xs.group_by { |x| x.role }Bucket. Returns Hash.
xs.tally / xs.tally_by { … }Frequency count.
xs.min_by { … } / max_by / sort_by"By" variants take a key function.
xs.lazy.select { … }.first(5)Lazy chains for large / infinite enumerables.
xs.any? / all? / none? / one?Predicate quantifiers.

OOP · mixinsClasses & modules

class User; endClass. Reopened on subsequent declarations.
class Admin < User; endSingle inheritance.
attr_reader / attr_writer / attr_accessor :nameGenerate getter / setter / both.
def initialize(…); endConstructor.
module M; def hello; "hi"; end; endModule. No instances; for mixins or namespacing.
include M / extend MMix into instances / mix into the class itself.
prepend MMix in above the class — module methods win.
Comparable + def <=>Define spaceship; gain ==, <, between?, etc.
Struct.new(:a, :b) do def total = a + b; endQuick value-type class. Equality + hash for free.
Data.define(:a, :b)Immutable Struct (Ruby 3.2+).
class << self / self.methodClass-level methods.
private / protected / publicVisibility. Apply downward inside class body.
private def fooPreferred Per-method visibility (modern style).

Worked example

ruby
# Modules as mixins — composition over inheritance
module Greetable
  def greet
    "hello, #{name}"
  end
end

# Comparable + <=> gives you ==, <, <=, >, >=, between?
class User
  include Greetable
  include Comparable
  attr_accessor :name, :score          # read + write accessors

  def initialize(name, score)
    @name, @score = name, score
  end

  def <=>(other)                       # define one operator → six come free
    score <=> other.score
  end

  def to_s = ""
end

ada   = User.new("Ada",   42)
grace = User.new("Grace", 99)

puts ada.greet
puts [ada, grace].max                  # uses <=>
puts ada.between?(grace, ada)          # false

# Pattern matching (Ruby 3+)
case ada
in { name:, score: } if score >= 50
  puts "#{name} qualifies"
in User(name:, score:)
  puts "#{name} below threshold (#{score})"
end

case/in (Ruby 3+)Pattern matching

case x in [a, b, *rest] then …Array destructure with splat tail.
case x in {name:, age:} then …Hash destructure. Binds name + age.
case x in {name: String => n} then …Type-check pattern + bind.
case x in User(name:, score: 50..) then …Class + sub-pattern. User needs deconstruct_keys.
case x in a | b then …Alternative patterns.
in pattern if guard then …Guard clause.
x => patternSingle-line match. Raises if it doesn’t match.
x in patternBoolean match. Returns true/false.

begin · rescue · ensureExceptions

begin … rescue => e … endGeneric rescue. Catches StandardError + subclasses.
rescue ArgumentError, KeyError => eSpecific types. Catch most-specific first.
retryRe-run the begin block from the top.
ensureAlways-run clause. For cleanup.
raise "msg" / raise ArgumentError, "msg"Throw. Default class is RuntimeError.
def f; rescue => e; … endMethod-level rescue. No begin needed.
class MyError < StandardError; endCustom error type.
Never rescue ExceptionCatches SIGTERM, NoMemoryError, … almost always wrong. Use StandardError.

Async HTTP fan-out · ~30 linesEnd-to-end · Concurrent HTTP

The async gem ships fiber-based scheduling. Three concurrent GitHub calls, JSON parsed, per-call error isolation.

ruby
# Gemfile:
# source "https://rubygems.org"
# gem "async-http"
# gem "async"

require "async"
require "async/http/internet"
require "async/barrier"
require "json"

OWNERS = %w[ruby sinatra rails]

Async do
  internet = Async::HTTP::Internet.new
  barrier  = Async::Barrier.new
  results  = {}

  OWNERS.each do |owner|
    barrier.async do
      url  = "https://api.github.com/users/#{owner}/repos?per_page=5"
      resp = internet.get(url, [["User-Agent", "devshelf"]])
      data = JSON.parse(resp.read)
      results[owner] = data.first(5).map { |r| [r["name"], r["stargazers_count"]] }
    rescue => e
      results[owner] = e
    end
  end

  barrier.wait

  results.each do |owner, data|
    if data.is_a?(Exception)
      warn "#{owner}: #{data.message}"
    else
      data.each { |name, stars| puts "#{owner}/#{name} ★#{stars}" }
    end
  end
ensure
  internet&.close
end

Best practiceGood to know

Prefer modules + composition over deep class trees. Ruby’s strength is mix-ins — include Comparable for free comparisons, include Enumerable for the entire collection API after defining each.
Bang (!) methods mutate. Convention: map returns a new array, map! mutates in place. Stick to this when writing your own; readers expect it.
Enable YJIT in production. Set RUBY_YJIT_ENABLE=1 (or pass --yjit). 15–30% speedup on Rails workloads with no code changes.

Common trapsWatch out for

nil and false are the only falsy values. Everything else, including 0 and empty strings, is truthy. Coming from Python or JS this catches everyone.
String mutation is in-place. s.upcase! changes the same object every reference points at — including hash keys, which then can’t be found anymore. Use String#freeze or the non-bang version.
return inside a Proc exits the enclosing method. That’s the LocalJumpError trap. Use a lambda when you want to return locally, or use next to exit the block.

Go deeperSee also

Ruby FAQ

What is Ruby used for?

Ruby is a dynamic, object-oriented scripting language known for its clean, expressive syntax. It is most widely used for web development with Rails and Sinatra, scripting and automation, CLI tools, and data processing. Ruby 3.3+ with YJIT delivers significantly faster performance than older versions.

What are Ruby blocks, procs, and lambdas?

A block is an anonymous chunk of code passed to a method with do...end or { }. A proc is a block saved in a variable with Proc.new or proc { }; it does not enforce argument count and returns from the enclosing method. A lambda is stricter — it checks argument count and returns from itself.

What is Enumerable in Ruby?

Enumerable is a mixin module included in Array, Hash, Range, and other collections. It provides map, select, reject, reduce, sort_by, group_by, flat_map, each_with_object, and dozens more. Any class that defines each and includes Enumerable gets all these methods automatically.

What is Ruby pattern matching?

Ruby 3.x added first-class pattern matching with the case/in syntax. Patterns can match values, arrays (in [a, b, *rest]), hashes (in { name: String => n }), find patterns (in [*, 0, *]), and pinned variables (in ^x). Use the one-line form expr => pattern for destructuring assignment.

Is Ruby still used in 2026?

Yes. Ruby remains the language of choice for Rails-based startups and established SaaS platforms. The ecosystem is mature: Rails 7, Hotwire, and Turbo handle full-stack web; Sidekiq powers background jobs; YJIT in Ruby 3.3+ closed much of the performance gap with compiled languages.