Metaprogramming in Ruby
Ruby lets you modify classes at runtime and build APIs dynamically—use responsibly.
Open classes
Reopen classes to add behavior. Prefer refinements in shared libraries.
class String
def shout
upcase + "!"
end
end
"hi".shout # => "HI!"
Dynamic methods
Define methods programmatically with define_method.
class Settings
[:host, :port].each do |name|
define_method(name) { @data[name] }
end
def initialize(data) @data = data end
end
method_missing and respond_to_missing?
Intercept unknown calls. Always pair with respond_to_missing?.
class Proxy
def method_missing(name, *args, &blk)
if name.to_s.start_with?("find_by_")
key = name.to_s.split("find_by_").last.to_sym
return lookup(key, *args)
end
super
end
def respond_to_missing?(name, include_private = false)
name.to_s.start_with?("find_by_") || super
end
end
Sending messages and singleton methods
Call methods dynamically; public_send respects visibility. Attach behavior to a single object.
obj.public_send(:to_s)
obj.define_singleton_method(:tag) { :special }
obj.tag # => :special
Refinements
Scope monkey patches to specific files/contexts.
module Shout
refine String do
def shout; upcase + "!" end
end
end
using Shout
"hi".shout # => "HI!"
Hooks
React to events like subclassing and method addition.
class Base
def self.inherited(subclass)
puts "#{subclass} < #{self}"
end
def self.method_added(name)
# called when an instance method is added
end
end
Cautions
- Prefer explicit code; use metaprogramming to eliminate duplication
- Keep
method_missingnarrow and documented; implementrespond_to_missing? - Be mindful of tooling and readability
Summary
- Use
define_method,public_send, and refinements judiciously - Provide
respond_to_missing?when overridingmethod_missing - Hooks and singleton methods enable dynamic, targeted behavior