Ruby Metaprogramming
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