8

Dynamic Ruby Features and Disciplined Metaprogramming

Ruby source files are converted directly from the English source tree.

en/topic_08_ruby_specific_features

Overview

Why this topic matters ๐Ÿ’ก

Ruby allows powerful runtime behavior, but the educational goal is restraint as much as capability. Students should see why dynamic dispatch and method generation can be beautiful, and also why overuse turns code into a puzzle.

Learning outcomes ๐ŸŽฏ

By the end of this topic, students should be able to:

  • explain what public_send does and why it is safer than send;
  • use define_method for repetitive, pattern-based APIs;
  • identify when dynamic code improves clarity and when it hurts maintainability;
  • explain why open classes are powerful and risky;
  • evaluate a metaprogramming technique based on readability, not novelty.

Assessment focus โœ…

Students should be able to justify dynamic behavior with a real use case instead of using it for style points.

Short Note

This topic is where students often first feel Ruby's "magic." The important lesson is not that Ruby can do dynamic tricks. The lesson is that dynamic features are most valuable when they remove repetitive code or express a tiny DSL clearly.

Ruby beauty in this topic:

  • public_send makes dynamic dispatch explicit;
  • define_method can remove repetitive boilerplate;
  • small internal DSLs can read close to domain language.

Ruby caution in this topic:

  • open classes can surprise the rest of the system;
  • generated methods can become invisible to readers and tools;
  • metaprogramming that saves five lines but costs ten minutes of comprehension is usually a bad trade.

Reflection prompt:

  • What makes a metaprogramming use case elegant rather than clever?

Worked Examples

Example 1: Metrics APIs ๐Ÿ’ก

Metrics systems often expose families of methods with the same shape:

  • track_cpu
  • track_memory
  • track_latency

That makes define_method a reasonable teaching example because the repetition is real.

Example 2: Query builder DSL ๐Ÿ’ก

Query-building code is a good example of dynamic Ruby used for readability rather than novelty.

QueryBuilder.new.where(:status, "active").where(:role, "admin")
worked_examples.md
ruby

Why this is useful:

  • the API reads like a small language;
  • method chaining returns the receiver intentionally;
  • the result shows how Ruby can make ordinary object interaction feel elegant.

Cheatsheet

Dynamic dispatch

public_send(operation, a, b)
cheatsheet.md
ruby

Method generation

[:cpu, :memory].each do |name|
  define_method("track_#{name}") do |value|
    "tracking #{name}=#{value}"
  end
end
cheatsheet.md
ruby

Chainable API

def where(key, value)
  @query[key] = value
  self
end
cheatsheet.md
ruby

Design reminders โš–๏ธ

  • Use dynamic techniques to remove repetition or express domain vocabulary.
  • Avoid them when plain methods would be clearer.

Study Guide

Topic purpose ๐ŸŽฏ

Learn how Ruby's dynamic features can make code expressive when used with discipline. This topic should teach admiration and skepticism at the same time.

Study sequence ๐Ÿชœ

  1. Read overview.md.
  2. Read shortnote.md before touching the exercises.
  3. Study worked_examples.md and ask whether each dynamic feature earns its cost.
  4. Keep cheatsheet.md open while reading example.rb.
  5. Complete the metrics exercise.
  6. Complete the advanced query-builder exercise.

What to notice ๐Ÿ”Ž

  • public_send is a deliberate runtime dispatch tool.
  • define_method is strongest when method patterns are regular and obvious.
  • Chained APIs can feel beautiful when they stay honest and small.

Reflection questions ๐Ÿค”

  • Why is the query builder example stronger than a random metaprogramming demo?
  • What makes define_method justified in the metrics exercise?
  • When would plain explicit methods be the better choice?

Source Files and Tests

Ruby source files are converted directly from the English source tree.

# EXAMPLE CODE
# Topic: topic_08_ruby_specific_features
#
# Purpose:
# - This file demonstrates reference implementation for the concept.
# - It should pass tests from the beginning.
# - Read and understand it before solving exercises.

class CalculatorOps
  def add(a, b) = a + b
  def sub(a, b) = a - b

  def apply(operation, a, b)
    public_send(operation, a, b)
  end
end
en/topic_08_ruby_specific_features/example.rb
Ruby course source
# STUDENT TASK (BASIC)
# Topic: topic_08_ruby_specific_features
#
# What to do:
# - Implement or improve the class/methods in this file.
# - Read tests in tests/topic_08_ruby_specific_features_spec.rb under the "basic exercise" examples.
# - Make tests pass without breaking the example/advanced sections.
#
# Expected outcome:
# - You can run this topic tests and see all examples green after implementation.

class Metrics
  [:cpu, :memory].each do |name|
    define_method("track_#{name}") do |value|
      "tracking #{name}=#{value}"
    end
  end
end
en/topic_08_ruby_specific_features/basic_exercise.rb
Ruby course source
# STUDENT TASK (ADVANCED)
# Topic: topic_08_ruby_specific_features
#
# Academic purpose:
# - Demonstrate a dynamic Ruby pattern that feels close to domain language rather than mere syntax trickery.
# - Evaluate metaprogramming by readability and usefulness.
#
# Real-world use case:
# - Query builders appear in ORMs, search interfaces, filtering layers, and internal DSLs.
# - Chaining methods such as `where(:status, "active")` can make intent very readable.
# - This is a better teaching example than abstract runtime dispatch because students can imagine using it.
#
# Why Ruby is beautiful here:
# - Returning `self` makes the API feel fluid.
# - The method names become part of a tiny language for expressing intent.
# - The resulting object can stay simple while the call site becomes expressive.
#
# What to do:
# - Complete the challenge behavior requested by the guide.
# - Be able to explain why this is a good DSL-style example and where the pattern should stop.
# - Use tests in tests/topic_08_ruby_specific_features_spec.rb under the "advanced exercise" examples.
#
# Expected outcome:
# - Advanced tests pass and you can discuss both the beauty and the risk of dynamic APIs.

class QueryBuilder
  def initialize
    @query = {}
  end

  def where(key, value)
    @query[key] = value
    self
  end

  def to_h
    @query.dup
  end
end
en/topic_08_ruby_specific_features/adv_exercise.rb
Ruby course source
# ANSWER KEY (BASIC)
# Topic: topic_08_ruby_specific_features
#
# Solution idea:
# - The methods all share the same shape, so `define_method` removes repetition.
# - This is a reasonable use of metaprogramming because the generated API is small and obvious.

class Metrics
  [:cpu, :memory].each do |name|
    define_method("track_#{name}") do |value|
      "tracking #{name}=#{value}"
    end
  end
end
en/topic_08_ruby_specific_features/answer_basic_exercise.rb
Ruby course source
# ANSWER KEY (ADVANCED)
# Topic: topic_08_ruby_specific_features
#
# Solution idea:
# - Build a chainable API by storing filter pairs in an internal hash.
# - Return `self` from `where` so multiple calls can read like a tiny query DSL.
# - Return a duplicated hash so callers do not mutate internal state accidentally.

class QueryBuilder
  def initialize
    @query = {}
  end

  def where(key, value)
    @query[key] = value
    self
  end

  def to_h
    @query.dup
  end
end
en/topic_08_ruby_specific_features/answer_adv_exercise.rb
Ruby course source
# This spec is your learning companion for topic_08_ruby_specific_features.
#
# How to use this file:
# 1) Run tests and observe failures/successes.
# 2) Keep EXAMPLE specs green from the beginning.
# 3) Implement BASIC exercise until BASIC specs pass.
# 4) Implement ADVANCED exercise and pass edge cases.
#
# Expected final result:
# - All examples in this file pass.
# - You understand both the concept and the implementation tradeoffs.

require_relative "../example"
require_relative "../basic_exercise"
require_relative "../adv_exercise"

RSpec.describe "topic_08_ruby_specific_features" do
  describe "EXAMPLE purpose: understand the reference implementation" do
    it "applies dynamic operation via public_send" do
      c = CalculatorOps.new
      expect(c.apply(:add, 2, 3)).to eq(5)
      expect(c.apply(:sub, 7, 4)).to eq(3)
    end
  end

  describe "BASIC EXERCISE purpose: implement the comparable task" do
    it "tracks dynamically defined metrics" do
      m = Metrics.new
      expect(m.track_cpu(70)).to eq("tracking cpu=70")
      expect(m.track_memory(256)).to eq("tracking memory=256")
    end
  end

  describe "ADVANCED EXERCISE purpose: solve challenge and edge cases" do
    it "builds chainable query hashes" do
      q = QueryBuilder.new.where(:status, "active").where(:role, "admin")
      expect(q.to_h).to eq({ status: "active", role: "admin" })
    end
  end
end
en/topic_08_ruby_specific_features/tests/topic_08_ruby_specific_features_spec.rb
Ruby course source
#!/usr/bin/env bash
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"

exec "${ROOT_DIR}/run_tests.sh" "$(basename "${SCRIPT_DIR}")"
en/topic_08_ruby_specific_features/run_topic_tests.sh
Ruby course source

Study Prompts

  1. Read the spec first and identify which behaviors belong to the example, basic exercise, and advanced exercise.

  2. Attempt the exercises before opening the answer files, then compare the design choices.