Introduction
Ruby metaprogramming allows generating code at runtime, creating powerful DSLs and flexible frameworks. In 2026, it remains essential for modern gems and high-performance applications. This tutorial covers advanced techniques used in Rails and professional libraries. You will learn to manipulate methods dynamically while avoiding maintainability pitfalls.
Prerequisites
- Ruby 3.3+
- Solid knowledge of Ruby OOP
- Experience with gems and Bundler
- Configured terminal and editor
Dynamic Method Definition
class DynamicAPI
def self.define_endpoint(name, &block)
define_method(name) do |*args|
instance_exec(*args, &block)
end
end
end
api = Class.new(DynamicAPI)
api.define_endpoint(:users) { |id| "User #{id}" }
instance = api.new
puts instance.users(42)This technique creates methods on the fly via define_method. It is used in Rails routers. Pay attention to readability and debugging.
Implementing method_missing
class MethodProxy
def method_missing(name, *args, &block)
if name.to_s.start_with?('get_')
key = name.to_s.sub('get_', '')
return "Value for #{key}"
end
super
end
def respond_to_missing?(name, include_private = false)
name.to_s.start_with?('get_') || super
end
end
proxy = MethodProxy.new
puts proxy.get_usernamemethod_missing allows intercepting unknown calls. Always implement respond_to_missing for full compatibility with respond_to?.
Singletons and Eigenclasses
obj = Object.new
class << obj
def unique_behavior
"Comportement spécifique à cet objet"
end
end
puts obj.unique_behavior
def obj.another_method
"Autre méthode singleton"
endSingletons allow adding behavior to specific instances without modifying the class. Useful for mocks and configurations.
Refinements for Scoping
module StringExtensions
refine String do
def reverse_words
split.reverse.join(' ')
end
end
end
using StringExtensions
puts "hello world".reverse_wordsRefinements limit the scope of monkey-patches. They are essential in 2026 to avoid conflicts in large applications.
Creating a Mini DSL
class Workflow
def self.define(&block)
instance = new
instance.instance_eval(&block)
instance
end
def step(name, &block)
(@steps ||= []) << { name: name, action: block }
end
def run
@steps.each { |s| s[:action].call }
end
end
wf = Workflow.define do
step(:init) { puts 'Initialisation' }
step(:process) { puts 'Traitement' }
end
wf.runinstance_eval enables creating readable DSLs. This approach is used in RSpec and builders. Keep the scope controlled.
Best Practices
- Prefer define_method over eval for security
- Always document dynamically generated methods
- Use refinements rather than global monkey-patches
- Thoroughly test method_missing paths
- Limit eigenclass depth for readability
Common Errors to Avoid
- Forgetting respond_to_missing creates bugs with third-party libraries
- Using eval without sanitization exposes security vulnerabilities
- Refinements not activated with 'using' have no effect
- Overloading method_missing without super breaks the inheritance chain
Further Reading
Deepen these concepts with our advanced Ruby courses.