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
Repository
bastos/ruby-plu…ketplaceGitHub Stars
2
First Seen
Apr 16, 2026
Security Audits