Ruby 3 min read

Some Ruby Metaprogramming notes

🔮 Ruby Metaprogramming: A Practical Guide

Metaprogramming is one of Ruby’s most powerful features — it allows programs to modify themselves while running. Let’s explore essential metaprogramming techniques and tools, including method_missing, eigenclasses, singleton methods, and more.

🧠 Core Concepts of Metaprogramming

method_missing

Ruby allows you to intercept calls to undefined methods using method_missing. This enables dynamic method handling:

class Ghost
  def method_missing(method_name, *args)
    puts "You tried to call #{method_name}, but it doesn't exist!"
  end
end

Ghost.new.hello # => "You tried to call hello, but it doesn't exist!"
 

const_missing

Similar to method_missing, Ruby also lets you handle undefined constants using const_missing:

class Object
  def self.const_missing(name)
    puts "Undefined constant: #{name}"
  end
end

FooBar # => "Undefined constant: FooBar"
 

undef_method vs remove_method

  • undef_method disables a method in the current class and any ancestor.

  • remove_method removes it from the class, but if it exists in a superclass, it’s still accessible.

 
class Parent
  def hello; puts "Hello from Parent"; end
end

class Child < Parent
  remove_method :hello
end

Child.new.hello # => "Hello from Parent"

class AnotherChild < Parent
  undef_method :hello
end

AnotherChild.new.hello # => Error: undefined method `hello`
 

Kernel#local_variables

You can inspect the current local scope:

my_var = 42
p local_variables # => [:my_var]
 

🔒 Scope Gates

Ruby introduces scope gates with constructs like class, module, and def. These gates close over local variables:

my_var = 123

MyClass = Class.new do
  puts "#{my_var} in the class definition!" # => Works

  define_method :my_method do
    puts "#{my_var} in the method!" # => Also works, due to define_method being a block
  end
end
 

Regular def wouldn’t have access to my_var, but define_method does because it's a closure.

🧱 Block, Proc, Lambda and Arity

Arity

A function’s arity is the number of arguments it expects.

p = Proc.new { |a, b| [a, b] }
puts p.arity # => 2
 

Lambda has stricter arity checking than Proc.

🔁 Revisiting Method

You can extract methods and reuse them like objects

class MyClass
  def initialize(value)
    @x = value
  end

  def my_method
    @x
  end
end

obj = MyClass.new(1)
m = obj.method(:my_method)
puts m.call # => 1
 

unbind and remind methods

unbound = m.unbind
new_obj = MyClass.new(42)
puts unbound.bind(new_obj).call # => 42
 

🧬 Singleton Methods

Ruby allows you to define methods only on a single object, known as singleton methods.

str = "hello"

def str.shout?
  self == self.upcase
end

puts str.shout?           # => false
puts str.singleton_methods # => [:shout?]
 

⚠️ Deprecation Helpers

You can create your own deprecation system using metaprogramming:

class Book
  def title
    "1984"
  end

  def self.deprecate(old_method, new_method)
    define_method(old_method) do |*args, &block|
      warn "Warning: #{old_method}() is deprecated. Use #{new_method}()"
      send(new_method, *args, &block)
    end
  end

  deprecate :GetTitle, :title
end

Book.new.GetTitle
# => Warning: GetTitle() is deprecated. Use title()
# => "1984"
 

🪞 Understanding the Eigenclass

The eigenclass (also known as the singleton class or metaclass) allows you to define behavior specific to one object.

To explore more on this advanced topic, check out this Viblo article on eigenclass.

🧩 Conclusion

Metaprogramming in Ruby is both powerful and elegant. It allows you to:

  • Dynamically define or intercept methods.

  • Modify or remove existing behaviors.

  • Work with closures and evaluate code in flexible scopes.

Used wisely, it can make your code flexible and expressive. Used recklessly, it can make things unpredictable. Like any power tool — handle with care.