Metaprogramming, Fibers, DSLs, Design Patterns & Performance — a developer’s deep dive into Ruby’s most powerful paradigms
1. Metaprogramming
Metaprogramming is one of Ruby’s defining strengths. Rails’ ActiveRecord, RSpec’s DSL, and countless gems rely on it. Understanding it deeply separates intermediate Rubyists from experts.
method_missing & respond_to_missing?
When Ruby can’t find a method, it calls method_missing. Pair it with respond_to_missing? to maintain correct introspection behavior.
👉 Example:
# A flexible proxy that delegates to a target object
class SmartProxy
def initialize(target)
@target = target
@call_log = []
end
def call_log
@call_log.dup
end
# Intercept every unknown method call
def method_missing(name, *args, **kwargs, &block)
if @target.respond_to?(name)
@call_log << { method: name, args: args, at: Time.now }
@target.public_send(name, *args, **kwargs, &block)
else
super
end
end
# CRITICAL: always pair with respond_to_missing?
def respond_to_missing?(name, include_private = false)
@target.respond_to?(name, include_private) || super
end
end
# Usage
proxy = SmartProxy.new("hello world")
proxy.upcase # => "HELLO WORLD"
proxy.split(" ") # => ["hello", "world"]
proxy.call_log.length # => 2Rubydefine_method & class_eval
Use define_method to dynamically generate methods inside a class body. Combine with class_eval (alias: module_eval) to inject code into an existing class from the outside.
module Attributable
# Generates typed accessors with validation at class level
def typed_attr(name, type:, default: nil)
ivar = :"@#{name}"
define_method(name) do
instance_variable_get(ivar) || default
end
define_method(:"#{name}=") do |val|
raise TypeError, "#{name} must be #{type}" unless val.is_a?(type) || val.nil?
instance_variable_set(ivar, val)
end
end
end
class Product
extend Attributable
typed_attr :name, type: String, default: "Unnamed"
typed_attr :price, type: Numeric, default: 0
typed_attr :stock, type: Integer, default: 0
end
p = Product.new
p.name = "Ruby Gem"
p.price = 49.99
p.stock = "lots" # => TypeError: stock must be IntegerRubyObject#send and public_send
send can call private methods — use public_send in production code to respect visibility boundaries. Both accept a symbol or string method name.
⚡ Pro Tip
Prefer public_send over send when calling methods on external objects. It respects the method’s visibility contract and prevents accidental private method exposure.
class Formatter
FORMATTERS = %i[titleize truncate markdown_escape]
def format(text, *transformations)
transformations.inject(text) do |result, t|
raise ArgumentError, "Unknown: #{t}" unless FORMATTERS.include?(t)
public_send(t, result)
end
end
private
def titleize(s) = s.split.map(&:capitalize).join(" ")
def truncate(s, len = 50) = s.length > len ? "#{s[0,len]}…" : s
def markdown_escape(s) = s.gsub(/([_*\[\]`])/, '\\\1')
end
f = Formatter.new
f.format("hello world_test", :titleize, :markdown_escape)
# => "Hello World\_Test"RubyRuby Blocks, Procs & Lambdas
Three ways to package executable code — understanding their subtle but critical differences is essential for writing expressive, bug-free Ruby.
| Feature | Block | Proc.new / proc | Lambda / ->( ) |
|---|---|---|---|
| Object? | No (not first-class) | Yes (Proc) | Yes (Proc) |
| Arity check | Loose | Loose (ignores extras) | Strict (ArgumentError) |
return behavior | Returns from enclosing method | Returns from enclosing method | Returns from lambda only |
| Syntax | { ... } or do...end | Proc.new { } | ->(x) { x } or lambda { } |
| Best used for | One-time iteration, DSL blocks | Callbacks, deferred code | First-class functions, strict APIs |
Block as Explicit Object (&block)
# Capture and store a block for later use
class EventEmitter
def initialize
@handlers = Hash.new { |h, k| h[k] = [] }
end
def on(event, &handler) # & converts block → Proc
@handlers[event] << handler
self
end
def emit(event, *payload)
@handlers[event].each { |h| h.call(*payload) }
self
end
end
emitter = EventEmitter.new
emitter
.on(:login) { |user| puts "Welcome, #{user}!" }
.on(:login) { |user| puts "Audit: login from #{user}" }
.on(:logout) { |user| puts "Goodbye, #{user}!" }
emitter.emit(:login, "Alice")
# => Welcome, Alice!
# => Audit: login from AliceRubyProc vs Lambda — The Return Trap
# Proc return: exits the ENCLOSING method
def demo_proc
p = Proc.new { return "from proc" }
p.call
"never reached" # ← this line never runs
end
# Lambda return: exits only the LAMBDA
def demo_lambda
l = lambda { return "from lambda" }
l.call
"this IS reached" # ← this line runs
end
demo_proc # => "from proc"
demo_lambda # => "this IS reached"
# Modern stabby lambda — idiomatic Ruby
multiply = ->(x, y) { x * y }
double = multiply.curry.call(2) # currying!
double.call(7) # => 14
double.call(9) # => 18
# Composing lambdas with >> and <<
sanitize = ->(s) { s.strip.downcase }
slugify = ->(s) { s.gsub(/\s+/, "-") }
to_slug = sanitize >> slugify
to_slug.call(" Advanced Ruby ") # => "advanced-ruby"RubyRuby Modules, Mixins & Hooks
Ruby’s module system is far more than namespacing — it’s a powerful composition mechanism. Mastering it unlocks elegant, DRY, testable architectures.
Custom Comparable & Enumerable
class Version
include Comparable
attr_reader :major, :minor, :patch
def initialize(str)
@major, @minor, @patch = str.split(".").map(&:to_i)
end
# Only requirement: define <=> and Comparable gives you the rest
def <=>(other)
[@major, @minor, @patch] <=> [other.major, other.minor, other.patch]
end
def to_s = "#{@major}.#{@minor}.#{@patch}"
end
versions = ["2.1.0", "1.9.3", "3.0.1", "2.1.1"].map { Version.new(_1) }
versions.sort.map(&:to_s) # => ["1.9.3", "2.1.0", "2.1.1", "3.0.1"]
versions.max.to_s # => "3.0.1"
versions.min_by(&:minor).to_s # => "1.9.3"
# ─────────────────────────────────────────────
# Enumerable: implement each, get 50+ methods free
class DataStream
include Enumerable
def initialize(source)
@source = source
end
def each(&block)
@source.each_line.each(&block)
end
end
stream = DataStream.new("alice,30\nbob,25\ncarol,35")
stream
.map { _1.strip.split(",") }
.select { |_, age| age.to_i > 28 }
.sort_by { |_, age| age.to_i }
# => [["alice", "30"], ["carol", "35"]]RubyRuby Module Hooks: included, extended, prepended
# Classic pattern: ClassMethods via included hook
module Validatable
def self.included(base)
base.extend(ClassMethods)
base.instance_variable_set(:@validations, [])
end
module ClassMethods
def validates(attr, **rules)
@validations << { attr: attr, rules: rules }
end
def validations = @validations
end
def valid?
self.class.validations.all? do |v|
val = public_send(v[:attr])
v[:rules].all? { |rule, opt| check(val, rule, opt) }
end
end
private
def check(val, rule, opt)
case rule
when :presence then !val.nil? && !val.to_s.empty?
when :min_length then val.to_s.length >= opt
when :format then opt.match?(val.to_s)
else true
end
end
end
class User
include Validatable
attr_accessor :name, :email
validates :name, presence: true, min_length: 2
validates :email, presence: true, format: /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
end
u = User.new
u.name = "Alice"
u.email = "alice@example.com"
u.valid? # => trueRubyEnumerators & Lazy Evaluation
Ruby’s Enumerator is one of its most underappreciated classes. Combined with lazy evaluation, it enables memory-efficient processing of enormous — even infinite — sequences.
# Custom external Enumerator
fibonacci = Enumerator.new do |yielder|
a, b = 0, 1
loop do
yielder << a # same as yielder.yield(a)
a, b = b, a + b
end
end
# Lazy: process only what you need
fibonacci
.lazy
.select(&:even?)
.first(8)
# => [0, 2, 8, 34, 144, 610, 2584, 10946]
# ─────────────────────────────────────────────
# Chained lazy pipeline — processes line-by-line
# without loading the whole file into memory
def process_large_log(path)
File.foreach(path)
.lazy
.map { _1.chomp }
.select { _1.include?("ERROR") }
.map { _1.split(" | ") }
.first(100)
end
# ─────────────────────────────────────────────
# Enumerator::Chain (Ruby 2.6+)
evens = (2..Float::INFINITY).lazy.select(&:even?)
odds = (1..Float::INFINITY).lazy.select(&:odd?)
merged = evens + odds # Enumerator::Chain
merged.first(6) # => [2, 4, 6, 8, 10, 1]
# ─────────────────────────────────────────────
# Enumerator::ArithmeticSequence
(0...100).step(7).to_a # => [0, 7, 14, 21, ...]RubyMemory Insight
Without .lazy, map and select generate full intermediate arrays. On a 1 GB log file, this means keeping gigabytes in memory. Lazy evaluation processes one element at a time — constant O(1) memory regardless of input size.
2. Advanced OOP Concepts
It supports inheritance, polymorphism, and encapsulation, which help build scalable applications.
👉 Example:
class Animal
def speak
puts "Some sound"
end
end
class Dog < Animal
def speak
puts "Woof!"
end
end
animal = Animal.new
dog = Dog.new
animal.speak # "Some sound"
dog.speak # "Woof!"
Ruby3. Concurrency and Multithreading
It supports threads and fibers for concurrent programming.
👉 Example with Threads:
threads = []
3.times do |i|
threads << Thread.new do
puts "Thread #{i} is running"
sleep(1)
puts "Thread #{i} is finished"
end
end
threads.each(&:join)
Ruby4. Garbage Collection and Memory Management
It automatically handles memory with its garbage collector, but developers can optimize memory usage with techniques like object re-use and profiling.
👉 Example (Measuring memory):
require 'objspace'
arr = []
1000.times { arr << "memory".dup }
puts ObjectSpace.memsize_of(arr) # Shows memory size of the array
Ruby5. Ruby on Rails Basics
Rails is a popular web framework built on Ruby. It follows the MVC (Model-View-Controller) pattern.
👉 Example (Simple Rails Route):
# In config/routes.rb
Rails.application.routes.draw do
get 'welcome/index'
end
# In app/controllers/welcome_controller.rb
class WelcomeController < ApplicationController
def index
end
end
# In app/views/welcome/index.html.erb
<h1>Hello from Rails!</h1>
Ruby6. Testing in Ruby (RSpec & Minitest)
Writing tests ensures code reliability.
👉 Example with RSpec:
# spec/calculator_spec.rb
require 'rspec'
class Calculator
def add(a, b)
a + b
end
end
RSpec.describe Calculator do
it "adds two numbers" do
calc = Calculator.new
expect(calc.add(2, 3)).to eq(5)
end
end
Ruby7. Performance Optimization
Optimizing it code involves reducing memory usage, avoiding unnecessary loops, and using built-in methods efficiently.
👉 Inefficient vs Optimized Example:
# Inefficient
result = []
[1,2,3,4,5].each { |n| result << n * 2 }
# Optimized
result = [1,2,3,4,5].map { |n| n * 2 }
puts result.inspect # [2, 4, 6, 8, 10]
Ruby✅ With these advanced concepts, you are ready to build high-performance Ruby applications. Whether you aim to master Rails web apps, delve into metaprogramming, or optimize for large-scale systems, Ruby provides powerful tools to take your skills to the next level.
- Top 10 Programming Languages to Master in 2026: The Ultimate Guide for Developers
- 🚀 Mastering JavaScript Performance: 5 Proven Techniques for 2026
- Ruby / Advanced
- Ruby / Intermediate
- Ruby / Basic

