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
