GGistDev

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_missing narrow and documented; implement respond_to_missing?
  • Be mindful of tooling and readability

Summary

  • Use define_method, public_send, and refinements judiciously
  • Provide respond_to_missing? when overriding method_missing
  • Hooks and singleton methods enable dynamic, targeted behavior