Page Content

Tutorials

Metaprogramming In Ruby With Practical Code Examples

The ability to consider the program itself as data that may be altered at runtime is one of Ruby’s most potent and unique features: metaprogramming. Ruby is a very dynamic language that really shines in this situation.

What is Metaprogramming in Ruby?

Writing code that makes your life easier by writing code during runtime is known as metaprogramming. Formally, it refers to computer programs that write or modify other programs (or themselves) as their data; these programs frequently carry out tasks during runtime that would typically be completed at compile time.

You can examine and change the program’s state and structure while it’s executing in Ruby with metaprogramming. This feature makes Ruby perfect for expanding Ruby’s syntax to facilitate programming, especially when combined with Ruby’s usually dynamic nature, method-invocation syntax, and use of code blocks. This collection of methods is strongly related to the concept of developing Domain-Specific Languages (DSLs), in which methods resemble keywords.

Reflection, often known as introspection, is the capacity of a computer to analyze its own state and structure. Metaprogramming techniques usually require this ability, such as displaying methods or querying variable values.

You can also read Benchmarking In Ruby: Measuring Code Performance Effectively

Core Metaprogramming Techniques and Examples

Generally divided into overriding message dispatch and dynamically defining methods, Ruby provides a variety of mechanisms for implementing metaprogramming.

Dynamic Method Definition (Avoiding Boilerplate)

You can define methods algorithmically with metaprogramming if you frequently write boring, repetitive, or “boilerplate” code. The secret is to create a data structure that lists the variations between the methods and then iterate over it to define each one.

Using Module#define_method

For dynamically defining new methods, Module#define_method is the recommended and secure method. It takes a block, a Proc, or a Method object as the method body and a Symbol for the method name. Usually private, when used outside of a regular class definition block, this method requires access via class_eval or transmit.

Example: Generating Accessor Methods

Metaprogramming is exemplified by Ruby’s built-in accessor methods, such as attr_reader and attr_accessor, which build custom getter and setter methods using programmer-supplied attribute names.

An iterative process over a data structure could be used as a generic way to define comparable methods:

class GeneratedFetcher
  def fetch(how_many)
    puts "Fetching #{how_many ? how_many : "all"}."
  end

  # Define methods algorithmically
  [["one", 1], ["ten", 10], ["all", nil]].each do |name, number|
    # define_method creates the method fetch_one, fetch_ten, etc.
    define_method("fetch_#{name}") do
      fetch(number)
    end
  end
end

# Usage:
GeneratedFetcher.new.fetch_one # Fetching 1.
GeneratedFetcher.new.fetch_all # Fetching all.

Output

Fetching 1.
Fetching all.

In addition to saving space in the class listing and requiring less typing, this also makes it simpler to implement additional methods of this type in the future by altering the data structure.

Metaprogramming with String Evaluation (module_eval / eval)

Method definitions can also be created as strings with Ruby code, which can then be executed using one of the eval methods. Module#module_eval is a common example of this; module_eval executes the string inside the class or module context, adding new variables and methods as though they were manually typed into the definition. This method is occasionally required when using reflection inside a define_method block becomes difficult.

Example: Defining Numeric Operations using String Evaluation

By creating and assessing strings, this example defines the add_2, subtract_2, etc., methods on Numeric:

class Numeric
  [['add', '+'], ['subtract', '-'],
   ['multiply', '*'], ['divide', '/']].each do |method, operator|
    module_eval %{
      def #{method}_2
        self #{operator} 2
      end
    }
  end
end

puts 4.add_2        # => 6
puts 10.divide_2    # => 5
puts 7.subtract_2   # => 5
puts 3.multiply_2   # => 6

Output

6
5
5
6

Because the string interpolation is used in this technique, the code produced looks just like it was written by hand. Nonetheless, it is typically not advised to evaluate arbitrary strings if they originate from an unreliable source because this presents a security risk.

You can also read What Is Profiling In Ruby & How It Works With Code Examples

Overriding Message Dispatch (method_missing)

Ruby’s method_missing hook method is one of its most potent dynamic features. Ruby calls method_missing if an object receives a message (a method call) that it is unable to handle (that is, the method cannot be located after examining the class, the eigenclass, and all predecessors).

Creating your own method_missing implementation allows you to dynamically manage and intercept calls to undefined methods. This is commonly used for dynamic interface implementation, delegation, and proxies.

Example: Creating Dynamic Predicate Methods on Numeric

This example uses method_missing to allow syntax like 777.is_greater_than_123? dynamically:

# open Numeric class
class Numeric
  def method_missing(method_name, *args)
    # 1. Test if the method_name matches the desired syntax
    if method_name.to_s.match(/^is_greater_than_(\d+)\?$/)
      # 2. Capture the number
      the_other_number = $1.to_i
      # 3. Handle the call: return the comparison result
      self > the_other_number
    else
      # 4. If it doesn't match, let the ancestor method_missing handle it
      super
    end
  end
end

# Usage examples (with output)
puts 5.is_greater_than_3?    # => true
puts 2.is_greater_than_10?   # => false
puts 8.is_greater_than_8?    # => false

Output

true
false
false

Important Note: When overriding method_missing, you should also override respond_to? to ensure consistency when checking if the object supports the dynamically-handled method name.

Alias Chaining (Modifying Existing Methods)

Metaprogramming is used not just to define new methods, but also to dynamically modify existing ones through alias chaining.

The process is:

  • Create an alias for the method to be modified (saving the original version).
  • Redefine the original method with a new version.
  • The new version calls the unmodified version through the alias, adding custom functionality before or after the original logic.

Example: Augmenting an Existing Method

def hello                       # The original method
  puts "Hello World"
end

alias original_hello hello      # Step 1: Create an alias for the original implementation

def hello                       # Step 2: Redefine the method
  puts "Your attention please"  # Added functionality (Before)
  original_hello                # Step 3: Call the original method
  puts "This has been a test"   # Added functionality (After)
end

hello

Output

Your attention please
Hello World
This has been a test

The aliased name refers to a copy of the original method’s body, meaning if the method is subsequently redefined, the alias still invokes the original implementation.

Metaprogramming in Ruby allows programmers to treat the language not just as a tool for solving problems, but as a flexible foundation that can be molded to fit the problem domain, making it a highly expressive tool for complex software design.

You can also read Understanding What Is Rake In Ruby & Its Role In Automation

Agarapu Geetha
Agarapu Geetha
My name is Agarapu Geetha, a B.Com graduate with a strong passion for technology and innovation. I work as a content writer at Govindhtech, where I dedicate myself to exploring and publishing the latest updates in the world of tech.
Index