Metaprogramming in Ruby
The impressive dynamic nature of Ruby grants you the freedom to define methods and classes during runtime, and this is known as metaprogramming. By metaprogramming with Ruby, you have the ability to ask your code questions about itself during runtime, allowing you to accomplish tasks in a fraction of the time it may take to do the same task in another language.

What is metaprogramming?
According to Wikipedia:
Metaprogramming is the writing of computer programs that write or manipulate other programs (or themselves) as their data, or that do part of the work at compile time that would otherwise be done at runtime. In many cases, this allows programmers to get more done in the same amount of time as they would take to write all the code manually, or it gives programs greater flexibility to efficiently handle new situations without recompilation. Or, more simply put: Metaprogramming is writing code that writes code during runtime to make your life easier.
In this article, we’ll be looking at a few different examples of metaprogramming in Ruby, specifically in using the Sinatra and Rails frameworks.
Metaprogramming in Rails

Let’s start with a very basic example in the Rails framework. Let’s say you have a database table for users
with columns for name
and email
. Rails doesn’t know what columns you have for each user
in the database, but somehow the following query works fine.
User.find_by_email('harry@potter.com')
# => <user id: 123, email: 'harry@potter.com', name: 'Harry Potter'>
We never defined the find_by_email
method but it works just as expected for us. This is due to the Rails framework dynamically creating a new method based on the column names in the database. This is a simple example of metaprogramming since the method was never defined but instead generated during runtime.
A common term you may come across when looking into metaprogramming in Ruby is the monkey patch. The monkey patch is a way for a program to extend or modify system classes locally in an object-oriented language.
[1, 2, 3].to_s
=> "[1, 2, 3, 4]" class Array
def to_s
'[]'
end
end[1, 2, 3].to_s
=> "[]"
The example above is a rudimentary approach to demonstrating that we can reach into any class, even core ones like Array, and modify it on the fly.
Metaprogramming in Sinatra
The Sinatra framework has a Sinatra::Delegator
module which is used to delegate or assign method calls to a specified target
. The target is set to Sinatra::Application
by default.
In the example below, we first define a new method to the Module
class called delegar
which allows us to say that when you call a certain method, you call that method on a different object instead of the current one where it is being used. This is a simplified version of the delegate
class method, defined in Sinatra::Delegator
.
class Module
def delegar(method, to:)
define_method(method) do |*args, &block|
send(to).send(method, *args, &block)
end
end
end
When this method is called, it will define a new method whose job it is to delegate the work to another object.
class Receptionist
def phone(name)
puts "Hello #{name}, I've answered your call."
end
endclass Company
attr_reader :receptionist
delegar :phone, to: :receptionist def initialize
@receptionist = Receptionist.new
end
endcompany = Company.new
company.phone 'Quinton'
# => "Hello Quinton, I've answered your call."
In the example above, we call the phone
method on the Company
class, but it is actually the Receptionist
class that handles the call since we’ve delegated the phone
call to the Receptionist
.
Let’s take a look at another example of how you can define a missing method on the fly. When you call a method on an object, Ruby goes into the class and tries to find the method, if it does not find it keeps searching up the ancestors' chain. If the method is still not found, then the method_missing
method is called. If this happens, we can use the define_method
to create a new method dynamically. This isn’t much different than using the usual def
to create a method but it does allow us to keep our code DRY.
class Developer
def method_missing method, *args, &block
return super method, *args, &block
unless method.to_s =~ /^newApp_\w+/
self.class.send(:define_method, method) do
p "writing " + method.to_s.gsub(/^newApp_/, '').to_s
end
self.send method, *args, &block
end
end
developer = Developer.new
developer.newApp_frontend
# => "writing newApp_frontend"
developer.newApp_backend
# => "writing newApp_backend"
developer.newApp_debug
# => "writing newApp_debug"
In the code above, calling a method that doesn’t exist will trigger method_missing
. In this case, we only want to create a new method when the method name starts with "newApp_"
. By using this piece of code we can create thousands of new methods starting with "newApp_"
through the help of define_method
.
This article has only scratched the surface of metaprogramming in Ruby, it is a very powerful technique, but only when used correctly and sparingly. It can help you keep your code DRY and it can help with debugging, but it can also add confusion if used excessively. Hopefully, this intro to metaprogramming has sparked your curiosity to learn more about it!