Ruby / Advanced

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 # => 2
Ruby

define_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 Integer
Ruby

Object#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"
Ruby

Ruby Blocks, Procs & Lambdas

Three ways to package executable code — understanding their subtle but critical differences is essential for writing expressive, bug-free Ruby.

FeatureBlockProc.new / procLambda / ->( )
Object?No (not first-class)Yes (Proc)Yes (Proc)
Arity checkLooseLoose (ignores extras)Strict (ArgumentError)
return behaviorReturns from enclosing methodReturns from enclosing methodReturns from lambda only
Syntax{ ... } or do...endProc.new { }->(x) { x } or lambda { }
Best used forOne-time iteration, DSL blocksCallbacks, deferred codeFirst-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 Alice
Ruby

Proc 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"
Ruby

Ruby 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"]]
Ruby

Ruby 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?  # => true
Ruby

Enumerators & 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, ...]
Ruby

Memory Insight

Without .lazymap 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!"
Ruby

3. 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)
Ruby

4. 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
Ruby

5. 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>
Ruby

6. 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
Ruby

7. 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.

Leave a Reply

Your email address will not be published. Required fields are marked *