Metaprogramming
24.1 Dynamic Method Calls
obj.send(:private_method, arg) # calls any method (ignores visibility)
obj.public_send(:name) # respects visibility (preferred)
obj.respond_to?(:name)
method_name = "upcase"
"hi".public_send(method_name)24.2 Defining Methods Dynamically
class Product
STATUSES = %i[draft published archived]
STATUSES.each do |status|
define_method("#{status}?") { @status == status }
define_method("#{status}!") { @status = status; self }
end
def self.attribute(name, default: nil)
define_method(name) { instance_variable_get("@#{name}") || default }
define_method("#{name}=") { |value| instance_variable_set("@#{name}", value) }
end
attribute :title, default: "Untitled"
attribute :price, default: 0
end
p = Product.new
p.published!.published? # true
p.title # "Untitled"24.3 method_missing and respond_to_missing?
Always pair method_missing with respond_to_missing?. Consider defining real methods on first use for performance ("ghost methods" → defined methods).
class Config
def initialize(data = {}) = @data = data
def method_missing(name, *args)
key = name.to_s
if key.end_with?("=")
@data[key.chomp("=").to_sym] = args.first
elsif @data.key?(name)
@data[name]
else
super # important: fall back to NoMethodError
end
end
def respond_to_missing?(name, include_private = false)
name.to_s.end_with?("=") || @data.key?(name) || super
end
end
c = Config.new(host: "localhost")
c.host; c.port = 80; c.respond_to?(:host)24.4 class_eval, instance_eval, instance_exec
String.class_eval do # adds instance methods (like reopening the class)
def vowels = count("aeiou")
end
obj = Object.new
obj.instance_eval { @secret = 42 } # self = obj: access internals
obj.instance_variable_get(:@secret)
class Builder
def self.build(&block) = new.tap { it.instance_eval(&block) } # DSL foundation
def name(value) = @name = value
end
Builder.build { name "widget" }
callback = proc { |x| "#{x} from #{self.class}" }
"str".instance_exec(5, &callback) # pass arguments + change self24.5 Introspection and Reflection
klass = Object.const_get("BankAccount") # string → class
klass.instance_methods(false)
klass.private_instance_methods(false)
klass.method_defined?(:deposit)
obj.instance_variables
obj.instance_variable_set(:@balance, 100)
Module.const_defined?(:Foo)
ObjectSpace.each_object(Class).select { it < StandardError }.first(5)
Class.new(StandardError) { def message = "custom" } # anonymous class
Object.const_set(:DynamicError, Class.new(StandardError))
method(:puts).owner # Kernel24.6 Code Evaluation
eval("1 + 2") # avoid with untrusted input (code injection!)
binding.local_variables
TOPLEVEL_BINDING.eval("self")