Lesson 14 / 25
Metaprogramming: send, define_method and method_missing
Generate and dispatch methods dynamically, and know the costs.
Code that writes code
Metaprogramming means code that defines or changes code at run time, and Ruby makes it unusually easy. send(:method_name, *args) (or public_send, which respects visibility) calls a method chosen at run time. define_method creates methods programmatically, for example generating paid?, shipped? and cancelled? from a list of statuses. method_missing catches calls to undefined methods, enabling dynamic finders or proxies; when you use it, also define respond_to_missing? so respond_to? stays accurate. instance_variable_get/set, class_eval and instance_eval inspect and modify objects and classes. Hooks such as included, inherited and method_added let modules react when they are used. Frameworks such as Rails use metaprogramming heavily (for example has_many :orders defines a whole set of methods). In application code, use it sparingly: dynamic methods are harder to find with search, confuse IDEs and static analysis, and can be slower. Prefer explicit code until repetition clearly justifies generation.
Generating predicate methods and a careful method_missing
define_method for known names; method_missing with respond_to_missing?.
class Order
STATUSES = %i[placed paid shipped cancelled].freeze
attr_reader :status, :attributes
def initialize(status:, **attributes)
@status = status
@attributes = attributes
end
STATUSES.each do |s|
define_method(:"#{s}?") { status == s } # placed?, paid?, shipped?, cancelled?
end
def method_missing(name, *args)
key = name.to_s.delete_prefix("attr_").to_sym
return attributes.fetch(key) if name.start_with?("attr_") && attributes.key?(key)
super # keep NoMethodError for everything else
end
def respond_to_missing?(name, include_private = false)
(name.start_with?("attr_") && attributes.key?(name.to_s.delete_prefix("attr_").to_sym)) || super
end
end
o = Order.new(status: :paid, city: "Pune", total: 1200)
puts o.paid? # true
puts o.attr_city # Pune
puts o.respond_to?(:attr_total) # true
puts o.public_send(:shipped?) # falseA receptionist with a rulebook
method_missing is a receptionist who handles visitors asking for rooms that do not exist on the directory, using a rulebook. Useful, but if the rulebook is vague, nobody can tell which rooms really exist.
Quick check: What should you define alongside method_missing?
- initialize
- to_s
- each
- respond_to_missing? so respond_to? reports dynamic methods correctly
Answer
respond_to_missing? so respond_to? reports dynamic methods correctly — respond_to_missing? keeps introspection consistent with the dynamic behaviour.