ð ruby-metaprogramming
Use when working with Ruby metaprogramming features including dynamic method definition, method_missing, class_eval, define_method, and reflection.
Overview
Master Ruby's powerful metaprogramming capabilities to write code that writes code. Ruby's dynamic nature makes it exceptionally good at metaprogramming.
Dynamic Method Definition
define_method
class Person
[:name, :age, :email].each do |attribute|
define_method(attribute) do
instance_variable_get("@#{attribute}")
end
define_method("#{attribute}=") do |value|
instance_variable_set("@#{attribute}", value)
end
end
end
person = Person.new
person.name = "Alice"
puts person.name # "Alice"
class_eval and instance_eval
# class_eval - Evaluates code in context of a class
class MyClass
end
MyClass.class_eval do
def hello
"Hello from class_eval"
end
end
puts MyClass.new.hello
# instance_eval - Evaluates code in context of an instance
obj = Object.new
obj.instance_eval do
def greet
"Hello from instance_eval"
end
end
puts obj.greet
module_eval
module MyModule
end
MyModule.module_eval do
def self.info
"Module metaprogramming"
end
end
puts MyModule.info
Method Missing
Basic method_missing
class DynamicFinder
def initialize(data)
@data = data
end
def method_missing(method_name, *args)
if method_name.to_s.start_with?("find_by_")
attribute = method_name.to_s.sub("find_by_", "")
@data.find { |item| item[attribute.to_sym] == args.first }
else
super
end
end
def respond_to_missing?(method_name, include_private = false)
method_name.to_s.start_with?("find_by_") || super
end
end
users = [
{ name: "Alice", age: 30 },
{ name: "Bob", age: 25 }
]
finder = DynamicFinder.new(users)
puts finder.find_by_name("Alice") # {:name=>"Alice", :age=>30}
Const Missing
class DynamicConstants
def self.const_missing(const_name)
puts "Constant #{const_name} not found, creating it..."
const_set(const_name, "Dynamic value for #{const_name}")
end
end
puts DynamicConstants::SOMETHING # "Dynamic value for SOMETHING"
send and public_send
class Calculator
def add(x, y)
x + y
end
private
def secret_method
"This is private"
end
end
calc = Calculator.new
# send can call any method (including private)
puts calc.send(:add, 3, 4) # 7
puts calc.send(:secret_method) # "This is private"
# public_send only calls public methods
puts calc.public_send(:add, 3, 4) # 7
# calc.public_send(:secret_method) # NoMethodError
Class Macros
class ActiveModel
def self.attr_with_history(attribute)
define_method(attribute) do
instance_variable_get("@#{attribute}")
end
define_method("#{attribute}=") do |value|
history = instance_variable_get("@#{attribute}_history") || []
history << value
instance_variable_set("@#{attribute}_history", history)
instance_variable_set("@#{attribute}", value)
end
define_method("#{attribute}_history") do
instance_variable_get("@#{attribute}_history") || []
end
end
end
class Person < ActiveModel
attr_with_history :name
end
person = Person.new
person.name = "Alice"
person.name = "Alicia"
puts person.name_history.inspect # ["Alice", "Alicia"]
Singleton Methods
obj = "hello"
# Define method on single instance
def obj.shout
self.upcase + "!!!"
end
puts obj.shout # "HELLO!!!"
# Using define_singleton_method
obj.define_singleton_method(:whisper) do
self.downcase + "..."
end
puts obj.whisper # "hello..."
Eigenclass (Singleton Class)
class Person
def self.species
"Homo sapiens"
end
end
# Accessing eigenclass
eigenclass = class << Person
self
end
puts eigenclass # #<Class:Person>
# Adding class methods via eigenclass
class Person
class << self
def count
@@count ||= 0
end
def increment_count
@@count ||= 0
@@count += 1
end
end
end
Person.increment_count
puts Person.count # 1
Reflection and Introspection
Object Introspection
class MyClass
def public_method; end
protected
def protected_method; end
private
def private_method; end
end
obj = MyClass.new
# List methods
puts obj.methods.include?(:public_method)
puts obj.private_methods.include?(:private_method)
puts obj.protected_methods.include?(:protected_method)
# Check method existence
puts obj.respond_to?(:public_method) # true
puts obj.respond_to?(:private_method) # false
puts obj.respond_to?(:private_method, true) # true (include private)
# Get method object
method = obj.method(:public_method)
puts method.class # Method
Class Introspection
class Parent
def parent_method; end
end
class Child < Parent
def child_method; end
end
# Inheritance chain
puts Child.ancestors # [Child, Parent, Object, Kernel, BasicObject]
# Instance methods
puts Child.instance_methods(false) # Only Child's methods
# Class variables and instance variables
class Person
@@count = 0
def initialize(name)
@name = name
end
end
puts Person.class_variables # [:@@count]
person = Person.new("Alice")
puts person.instance_variables # [:@name]
Hook Methods
Inheritance Hooks
class BaseClass
def self.inherited(subclass)
puts "#{subclass} inherited from #{self}"
subclass.instance_variable_set(:@inherited_at, Time.now)
end
end
class ChildClass < BaseClass
end
# Output: ChildClass inherited from BaseClass
Method Hooks
module Monitored
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def method_added(method_name)
puts "Method #{method_name} was added to #{self}"
end
def method_removed(method_name)
puts "Method #{method_name} was removed from #{self}"
end
end
end
class MyClass
include Monitored
def my_method
end
# Output: Method my_method was added to MyClass
end
included and extended
module MyModule
def self.included(base)
puts "#{self} included in #{base}"
base.extend(ClassMethods)
end
def self.extended(base)
puts "#{self} extended by #{base}"
end
module ClassMethods
def class_method
"I'm a class method"
end
end
def instance_method
"I'm an instance method"
end
end
class MyClass
include MyModule # Adds instance_method as instance method
end
class AnotherClass
extend MyModule # Adds instance_method as class method
end
DSL Creation
class RouteBuilder
def initialize
@routes = {}
end
def get(path, &block)
@routes[path] = { method: :get, handler: block }
end
def post(path, &block)
@routes[path] = { method: :post, handler: block }
end
def routes
@routes
end
end
# DSL usage
builder = RouteBuilder.new
builder.instance_eval do
get "/users" do
"List of users"
end
post "/users" do
"Create user"
end
end
puts builder.routes
Dynamic Class Creation
# Create class dynamically
MyClass = Class.new do
define_method :greet do
"Hello from dynamic class"
end
end
puts MyClass.new.greet
# Create class with inheritance
Parent = Class.new do
def parent_method
"From parent"
end
end
Child = Class.new(Parent) do
def child_method
"From child"
end
end
child = Child.new
puts child.parent_method
puts child.child_method
Object Extension
module Greetable
def greet
"Hello!"
end
end
obj = Object.new
obj.extend(Greetable)
puts obj.greet # "Hello!"
# Only this instance has the method
another_obj = Object.new
# another_obj.greet # NoMethodError
Binding and eval
def get_binding(param)
local_var = "local value"
binding
end
b = get_binding("test")
# Evaluate code in the binding context
puts eval("param", b) # "test"
puts eval("local_var", b) # "local value"
# instance_eval with binding
class MyClass
def initialize
@value = 42
end
end
obj = MyClass.new
puts obj.instance_eval { @value } # 42
TracePoint
trace = TracePoint.new(:call, :return) do |tp|
puts "#{tp.event}: #{tp.method_id} in #{tp.defined_class}"
end
trace.enable
def my_method
"Hello"
end
my_method
trace.disable
Best Practices
- Use metaprogramming sparingly - it can make code hard to understand
- Always implement respond_to_missing? when using method_missing
- Prefer define_method over class_eval when possible
- Document metaprogramming heavily - it's not obvious what's happening
- Use public_send over send to respect visibility
- Cache metaprogrammed methods to avoid repeated definition
- Test metaprogrammed code thoroughly - bugs can be subtle
Anti-Patterns
â Don't overuse method_missing - it's slow and hard to debug â Don't use eval with user input - major security risk â Don't metaprogram when simple code works - clarity over cleverness â Don't forget to call super in method_missing â Don't create methods without documenting them - IDE support breaks
Common Use Cases
- ORMs (ActiveRecord) - Dynamic finders, associations
- DSLs - Route definitions, configurations
- Decorators - Method wrapping and enhancement
- Mocking/Stubbing - Test frameworks
- Attribute definition - Custom accessors with behavior
Related Skills
- ruby-oop - Understanding classes and modules
- ruby-blocks-procs-lambdas - For callbacks and dynamic behavior
- ruby-gems - Many gems use metaprogramming extensively