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_senddoes and why it is safer thansend; - use
define_methodfor 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_sendmakes dynamic dispatch explicit;define_methodcan 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_cputrack_memorytrack_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")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)ruby
Method generation
[:cpu, :memory].each do |name|
define_method("track_#{name}") do |value|
"tracking #{name}=#{value}"
end
endruby
Chainable API
def where(key, value)
@query[key] = value
self
endruby
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 ๐ช
- Read
overview.md. - Read
shortnote.mdbefore touching the exercises. - Study
worked_examples.mdand ask whether each dynamic feature earns its cost. - Keep
cheatsheet.mdopen while readingexample.rb. - Complete the metrics exercise.
- Complete the advanced query-builder exercise.
What to notice ๐
public_sendis a deliberate runtime dispatch tool.define_methodis 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_methodjustified 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
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
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
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
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
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
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}")"
Ruby course source
Study Prompts
Read the spec first and identify which behaviors belong to the example, basic exercise, and advanced exercise.
Attempt the exercises before opening the answer files, then compare the design choices.