ruby-core

Installation
SKILL.md

Ruby Core Language Features

Comprehensive guide to Ruby 3.x core language features, idioms, and best practices.

Blocks, Procs, and Lambdas

Blocks

Blocks are anonymous functions passed to methods:

# Block with do...end (multi-line)
[1, 2, 3].each do |n|
  puts n * 2
end

# Block with braces (single-line, returns value)
doubled = [1, 2, 3].map { |n| n * 2 }

# Yielding to blocks
def with_timing
  start = Time.now
  result = yield
  puts "Took #{Time.now - start}s"
  result
end

with_timing { expensive_operation }

# Block with arguments
def transform(value)
  yield(value) if block_given?
end

transform(5) { |n| n * 2 }  # => 10

# Capturing blocks as Proc
def save_block(&block)
  @callback = block
end

Procs vs Lambdas

# Proc - flexible argument handling, returns from enclosing method
my_proc = Proc.new { |a, b| a + b }
my_proc = proc { |a, b| a + b }

my_proc.call(1, 2)     # => 3
my_proc.call(1, 2, 3)  # => 3 (extra args ignored)
my_proc.call(1)        # => TypeError (nil + 1)

# Lambda - strict argument checking, returns to caller
my_lambda = ->(a, b) { a + b }
my_lambda = lambda { |a, b| a + b }

my_lambda.call(1, 2)     # => 3
my_lambda.call(1, 2, 3)  # ArgumentError
my_lambda.call(1)        # ArgumentError

# Key difference: return behavior
def proc_return
  p = Proc.new { return "from proc" }
  p.call
  "after proc"  # Never reached
end

def lambda_return
  l = -> { return "from lambda" }
  l.call
  "after lambda"  # This executes
end

Method Objects

# Convert method to proc
def double(n) = n * 2

[1, 2, 3].map(&method(:double))  # => [2, 4, 6]

# Unbound methods
unbound = String.instance_method(:upcase)
bound = unbound.bind("hello")
bound.call  # => "HELLO"

Enumerables

Core Enumerable Methods

numbers = [1, 2, 3, 4, 5]

# Transformation
numbers.map { |n| n * 2 }           # => [2, 4, 6, 8, 10]
numbers.flat_map { |n| [n, n * 2] } # => [1, 2, 2, 4, 3, 6, 4, 8, 5, 10]

# Filtering
numbers.select { |n| n.even? }      # => [2, 4]
numbers.reject { |n| n.even? }      # => [1, 3, 5]
numbers.filter_map { |n| n * 2 if n.even? }  # => [4, 8]

# Reduction
numbers.reduce(0) { |sum, n| sum + n }  # => 15
numbers.reduce(:+)                       # => 15
numbers.sum                              # => 15

# Searching
numbers.find { |n| n > 3 }          # => 4
numbers.find_index { |n| n > 3 }    # => 3
numbers.any? { |n| n > 3 }          # => true
numbers.all? { |n| n > 0 }          # => true
numbers.none? { |n| n > 10 }        # => true
numbers.one? { |n| n == 3 }         # => true

# Grouping
numbers.group_by { |n| n % 2 }      # => {1=>[1,3,5], 0=>[2,4]}
numbers.partition { |n| n.even? }   # => [[2,4], [1,3,5]]
numbers.tally                        # Count occurrences

# Ordering
numbers.sort_by { |n| -n }          # => [5, 4, 3, 2, 1]
numbers.max_by { |n| n % 3 }        # => 2
numbers.min_by { |n| n % 3 }        # => 3

# Chaining
numbers.lazy.select { |n| n.even? }.map { |n| n * 2 }.first(2)

Lazy Enumerables

# Process large/infinite sequences efficiently
(1..Float::INFINITY)
  .lazy
  .select { |n| n % 3 == 0 }
  .map { |n| n * 2 }
  .first(5)
# => [6, 12, 18, 24, 30]

# Memory-efficient file processing
File.foreach("large_file.txt")
    .lazy
    .map(&:chomp)
    .select { |line| line.include?("ERROR") }
    .first(10)

Pattern Matching (Ruby 3.x)

Basic Patterns

case value
in Integer => n if n > 0
  "positive integer: #{n}"
in Integer
  "non-positive integer"
in String => s
  "string: #{s}"
in [first, *rest]
  "array starting with #{first}"
in { name:, age: }
  "person: #{name}, #{age}"
in nil
  "nothing"
else
  "unknown"
end

Array Patterns

case [1, 2, 3]
in [a, b, c]
  "three elements: #{a}, #{b}, #{c}"
in [head, *tail]
  "head: #{head}, tail: #{tail}"
in [*, last]
  "last element: #{last}"
in [first, *, last]
  "first: #{first}, last: #{last}"
end

Hash Patterns

case { name: "Alice", age: 30, role: "admin" }
in { name: "Alice", role: }
  "Alice is #{role}"
in { name:, age: 18.. }
  "#{name} is an adult"
in { **rest }
  "other: #{rest}"
end

Guard Clauses

case number
in n if n.negative?
  "negative"
in n if n.zero?
  "zero"
in n if n.positive?
  "positive"
end

Pinning

expected = 42

case value
in ^expected
  "matched expected value"
in other
  "got #{other} instead of #{expected}"
end

Error Handling

Exception Basics

begin
  risky_operation
rescue SpecificError => e
  handle_specific_error(e)
rescue StandardError => e
  handle_general_error(e)
else
  # Runs if no exception
  success_callback
ensure
  # Always runs
  cleanup
end

# Inline rescue (use sparingly)
value = risky_operation rescue default_value

# Retry pattern
attempts = 0
begin
  attempts += 1
  unreliable_api_call
rescue NetworkError
  retry if attempts < 3
  raise
end

Custom Exceptions

class ApplicationError < StandardError; end

class ValidationError < ApplicationError
  attr_reader :field, :value

  def initialize(message, field:, value:)
    @field = field
    @value = value
    super(message)
  end
end

raise ValidationError.new("Invalid email", field: :email, value: input)

Exception Hierarchy

Exception
├── NoMemoryError
├── ScriptError
├── SignalException
├── SystemExit
└── StandardError (catch this, not Exception)
    ├── ArgumentError
    ├── IOError
    ├── NameError
    │   └── NoMethodError
    ├── RangeError
    ├── RuntimeError (default for `raise`)
    ├── TypeError
    └── ZeroDivisionError

Ruby 3.x Features

Data Classes (Ruby 3.2+)

# Immutable value objects
Point = Data.define(:x, :y)

p = Point.new(1, 2)
p.x           # => 1
p.with(x: 10) # => Point(x: 10, y: 2)

# With methods
Point = Data.define(:x, :y) do
  def distance_from_origin
    Math.sqrt(x**2 + y**2)
  end
end

Ractors (Parallel Execution)

# True parallelism without GVL
ractors = 4.times.map do |i|
  Ractor.new(i) do |n|
    # Runs in parallel
    heavy_computation(n)
  end
end

results = ractors.map(&:take)

Endless Methods

# Single-expression method shorthand
def double(n) = n * 2
def greet(name) = "Hello, #{name}!"
def square(n) = n ** 2

Refinements

module StringExtensions
  refine String do
    def shout
      upcase + "!"
    end
  end
end

class Greeter
  using StringExtensions

  def greet(name)
    "hello #{name}".shout  # => "HELLO NAME!"
  end
end

# Outside the class, shout is not available
"test".shout  # NoMethodError

Numbered Block Parameters

# Use _1, _2, etc. for simple blocks
[1, 2, 3].map { _1 * 2 }           # => [2, 4, 6]
[[1, 2], [3, 4]].map { _1 + _2 }   # => [3, 7]

Best Practices

Guard Clauses

# Prefer
def process(value)
  return if value.nil?
  return "empty" if value.empty?

  # main logic
end

# Over
def process(value)
  if value
    if value.empty?
      "empty"
    else
      # main logic
    end
  end
end

Safe Navigation

# Safe navigation operator
user&.profile&.avatar&.url

# With default
user&.profile&.avatar&.url || "default.png"

Frozen String Literals

# frozen_string_literal: true

# All string literals are frozen, improving performance
name = "Alice"  # Frozen, cannot be mutated
name << " Bob"  # FrozenError

# Use +@ to get mutable copy
mutable = +name
mutable << " Bob"  # OK
Weekly Installs
1
GitHub Stars
2
First Seen
Apr 16, 2026