Reading — step 1 of 7
Learn
Ruby is famously dynamic. The Pickaxe spends entire chapters on metaprogramming — code that writes code at runtime. Three pillars: send, define_method, and method_missing.
send — call a method by name
class User
def initialize(name); @name = name; end
def name; @name; end
end
u = User.new("Ada")
u.send(:name) # => "Ada" — same as u.name
u.send("name") # also works (string or symbol)
u.send(:name=, "Linus") # call setter dynamically
send even calls private methods — useful for testing internals, dangerous for security. Use public_send if you want to respect visibility.
Useful for dispatching by user input or config:
actions = { 'create' => :create_user, 'delete' => :destroy_user }
self.send(actions[input])
define_method — create methods at runtime
class Config
[:host, :port, :user, :password].each do |attr|
define_method(attr) { @data[attr] }
define_method("#{attr}=") { |v| @data[attr] = v }
end
end
This is how attr_accessor is implemented (roughly). Define many similar methods without repeating yourself.
Key trick: define_method takes a BLOCK, which is a closure — the block captures variables from the enclosing scope. Compare to def, which doesn't.
method_missing — handle unknown method calls
class DynamicAttrs
def initialize
@data = {}
end
def method_missing(name, *args)
name_str = name.to_s
if name_str.end_with?('=')
@data[name_str.chomp('=').to_sym] = args.first
else
@data[name]
end
end
def respond_to_missing?(name, include_private = false)
true
end
end
d = DynamicAttrs.new
d.foo = 42
d.foo # => 42 — went through method_missing
Always implement respond_to_missing? to match — otherwise respond_to?(:foo) returns false, breaking duck-typing checks throughout the standard library.
Reflection methods
User.instance_methods(false) # methods defined ON User (not inherited)
u.methods.grep(/age/) # all methods matching the pattern
User.method_defined?(:name)
u.respond_to?(:name)
u.method(:name).source_location # [file, line] where it was defined
At runtime, you can introspect anything.
When to reach for metaprogramming
Good fits:
- DSLs (RSpec, ActiveRecord, Rails routes)
- Reducing repetition for parallel methods (like attr_accessor)
- Adapters that wrap dynamic APIs
Avoid for:
- Anything you could write with normal methods + a hash lookup
- Anything performance-critical (method_missing is slow)
- Any code where readers will be confused
Metaprogramming is the single biggest source of "I can't find where this method is defined" headaches. Use sparingly.
Common mistakes
- Implementing method_missing without respond_to_missing? — silently breaks duck typing.
- Overusing send — bypasses encapsulation, harder to refactor.
- define_method inside def — wrong scope for the block. Use it at class scope.
- Catching too much in method_missing — missing methods (typos, etc.) get swallowed silently. Always check the name and call super for unknowns.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…