पाठ 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?)    # false

A 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.

त्वरित जाँच: 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.