Technology10-minute read

Ruby Metaprogramming Is Even Cooler Than It Sounds

Ruby metaprogramming, one of the most interesting aspects of Ruby, enables the programming language to achieve an extreme level of expressiveness. It is because of this very feature that many gems, such as RSpec and ActiveRecord, can work the way they do. In this article, Toptal engineer Nikola Todorovic demystifies Ruby metaprogramming using some examples that are relevant to everyday programming and aims to bring it closer to average Ruby developers.

Last updated: Apr 28, 2026

Toptalauthors are vetted experts in their fields and write on topics in which they have demonstrated experience. All of our content is peer reviewed and validated by Toptal experts in the same field.

Ruby metaprogramming, one of the most interesting aspects of Ruby, enables the programming language to achieve an extreme level of expressiveness. It is because of this very feature that many gems, such as RSpec and ActiveRecord, can work the way they do. In this article, Toptal engineer Nikola Todorovic demystifies Ruby metaprogramming using some examples that are relevant to everyday programming and aims to bring it closer to average Ruby developers.

Last updated: Apr 28, 2026

Toptalauthors are vetted experts in their fields and write on topics in which they have demonstrated experience. All of our content is peer reviewed and validated by Toptal experts in the same field.
Nikola Todorovic
19 Years of Experience

Nikola has an MCE degree and almost a decade of experience in software development. His passions are Ruby on Rails and startups.

Previous Role

Senior Back-end Ruby Developer
Share

You often hear that metaprogramming is something that only Ruby ninjas use, and that it simply isn’t for common mortals. But the truth is that metaprogramming isn’t something scary at all. This blog post will serve to challenge this type of thinking and to get metaprogramming closer to the average Ruby developer so that they can also reap its benefits.

That said, metaprogramming can mean a lot of things, and it’s easy to take it too far. So instead of diving into anything overly abstract, we’ll focus on practical, real-world examples you can use in everyday programming.

Metaprogramming

Metaprogramming is a technique by which you can write code that writes code by itself dynamically at runtime. This means you can define methods and classes during runtime. Crazy, right? In a nutshell, using metaprogramming you can reopen and modify classes, catch methods that don’t exist and create them on the fly, create code that is DRY by avoiding repetitions, and more.

The Basics

Before we dive into serious metaprogramming, we must explore the basics. And the best way to do that is by example. Let’s start with one and understand Ruby metaprogramming step-by-step. You can probably guess what this code is doing:

class Developer

  def self.backend
    "I am backend developer"
  end
  
  def frontend
    "I am frontend developer"
  end

end

We have defined a class with two methods. The first method in this class is a class method and the second one is an instance method. This is basic stuff in Ruby, but there is much more happening behind this code which we need to understand before we proceed further. It is worth pointing out that the class Developer itself is actually an object. In Ruby, everything is an object, including classes. Since Developer is an instance, it is an instance of class Class. Here is how the Ruby object model looks like:

Ruby object model

p Developer.class # Class
p Class.superclass # Module
p Module.superclass # Object
p Object.superclass # BasicObject

One important thing to understand here is the meaning of self. The frontend method is a regular method that is available on instances of class Developer, but why is backend method a class method? Every piece of code executed in Ruby is executed against a particular self. When the Ruby interpreter executes any code, it always keeps track of the value self for any given line. self is always referring to some object, but that object can change based on the code executed. For example, inside a class definition, the self refers to the class itself, which is an instance of class Class.

class Developer
  p self 
end
# Developer

Inside instance methods, self refers to an instance of the class.

class Developer
  def frontend
    self
  end
end
 
p Developer.new.frontend
# #<Developer:0x2c8a148>

Inside class methods, self refers to the class itself in a way (which will be discussed in more detail later in this article):

class Developer
  def self.backend
    self
  end
end

p Developer.backend
# Developer

This is fine, but what is a class method after all? Before answering that question, we need to mention the existence of something called a metaclass, also known as a singleton class and eigenclass. Class method backend that we defined earlier is nothing but an instance method defined in the metaclass for the object Developer! A metaclass is essentially a class that Ruby creates and inserts into the inheritance hierarchy to hold class methods, thus not interfering with instances that are created from the class.

Metaclasses

Every object in Ruby has its own metaclass. It is somehow invisible to a developer, but it is there, and you can use it very easily. Since our class Developer is essentially an object, it has its own metaclass. As an example, let’s create an object of the class String and manipulate its metaclass:

example = "I'm a string object"

def example.something
  self.upcase
end

p example.something
# I'M A STRING OBJECT

What we did here is we added a singleton method something to an object. The difference between class methods and singleton methods is that class methods are available to all instances of a class object, while singleton methods are available only to that single instance. Class methods are widely used, while singleton methods are not so much, but both types of methods are added to the metaclass of that object.

The previous example could be rewritten like this:

example = "I'm a string object"

class << example
  def something
    self.upcase
  end
end

The syntax is different, but it effectively does the same thing. Now let’s go back to the previous example where we created the Developer class and explore some other syntaxes to define a class method:

class Developer
  def self.backend
    "I am backend developer"
  end
end

This is a basic definition that almost everybody uses.

def Developer.backend
  "I am backend developer"
end

This is the same thing; we are defining the backend class method for Developer. We didn’t use self, but defining a method like this effectively makes it a class method.

class Developer
  class << self
    def backend
      "I am backend developer"
    end
  end
end

Again, we are defining a class method, but using syntax similar to one we used to define a singleton method for a String object. You may notice that we used self here, which refers to the Developer object itself. First, we opened the Developer class, making self equal to the Developer class. Next, we do class << self, making self equal to Developer’s metaclass. Then we define a method backend on Developer’s metaclass.

class << Developer
  def backend
    "I am backend developer"
  end
end

By defining a block like this, we are setting self to Developer’s metaclass for the duration of the block. As a result, the backend method is added to Developer’s metaclass, rather than the class itself.

Let’s see how this metaclass behaves in the inheritance tree:

Metaclass in inheritance tree

Although there’s no obvious way to “see” a metaclass directly, Ruby provides a built-in method called singleton_class that gives us access to it. For example:

p developer.class.singleton_class.instance_methods false
# [:backend]

This gives us a direct way to inspect methods defined on the metaclass.

If you’re curious about how this works under the hood, we can use a small hack to expose the metaclass manually:

class Object
  def metaclass_example
    class << self
      self
    end
  end
end

If we define an instance method in the Object class (yes, we can reopen any class anytime, that’s yet another beauty of metaprogramming), we will have a self referring to the Object object inside it. We can then use class << self syntax to change the current self to point to the metaclass of the current object. Since the current object is the Object class itself, this would be the instance’s metaclass. The method returns self, which is, at this point, a metaclass itself. So by calling this instance method on any object, we can get the metaclass of that object. Let’s define our Developer class again and start exploring a little:

class Developer

  def frontend
    p "inside instance method, self is: " + self.to_s
  end

  class << self
    def backend
      p "inside class method, self is: " + self.to_s
    end
  end
  
end

developer = Developer.new
developer.frontend
# "inside instance method, self is: #<Developer:0x2ced3b8>"

Developer.backend
# "inside class method, self is: Developer"

p "inside metaclass, self is: " + developer.metaclass_example.to_s
# "inside metaclass, self is: #<Class:#<Developer:0x2ced3b8>>"

And for the crescendo, let’s see the proof that frontend is an instance method of a class and backend is an instance method of a metaclass:

p developer.class.instance_methods false
# [:frontend]

- p developer.class.metaclass_example.instance_methods false
+ p developer.class.singleton_class.instance_methods false
# [:backend]

Defining Methods Using “class_eval” and “instance_eval”

There’s one more way to create a class method, and that is by using instance_eval:

class Developer
end

Developer.instance_eval do
  p "instance_eval - self is: " + self.to_s
  def backend
    p "inside a method self is: " + self.to_s
  end
end
# "instance_eval - self is: Developer"

Developer.backend
# "inside a method self is: Developer"

The Ruby interpreter evaluates this piece of code in the context of an object, which in this case is the Developer class. And when you are defining a method on an object, you are creating either a class method or a singleton method. In this case, it is a class method—to be exact, class methods are just singleton methods defined on a class object, while other singleton methods are defined on regular objects.

On the other hand, class_eval evaluates the code in the context of a class instead of an object. It practically reopens the class. Here is how class_eval can be used to create an instance method:

Developer.class_eval do
  p "class_eval - self is: " + self.to_s
  def frontend
    p "inside a method self is: " + self.to_s
  end
end
# "class_eval - self is: Developer"

p developer = Developer.new
# #<Developer:0x2c5d640>

developer.frontend
# "inside a method self is: #<Developer:0x2c5d640>"

When you call instance_eval, self becomes the object itself (in this case, the Developer class). Any methods you define inside the block become singleton methods of that object, meaning they live in its metaclass. In other words, class_eval defines instance methods on a class, while instance_eval defines singleton methods on an object.

Defining Missing Methods on the Fly

One more piece of the metaprogramming puzzle is method_missing. When you call a method on an object, Ruby first goes into the class and browses its instance methods. If it doesn’t find the method there, it continues the search up the ancestors chain. If Ruby still doesn’t find the method, it calls another method named method_missing, which is an instance method of Kernel that every object inherits. Since we are sure that Ruby is going to call this method eventually for missing methods, we can use this to implement some tricks.

define_method is a method defined in the Module class that you can use to create methods dynamically. You call it with the name of the new method and a block where the block parameters become the parameters of the new method. It works especially well in combination with method_missing to write DRY code. Let’s take a look at a simple example:

class Developer
  define_method :frontend do |*my_arg|
    my_arg.inject(1, :*)
  end

  class << self
    def create_backend
      singleton_class.send(:define_method, "backend") do
        "Born from the ashes!"
      end
    end
  end
end

developer = Developer.new
p developer.frontend(2, 5, 10)
# => 100

p Developer.backend
# undefined method 'backend' for Developer:Class (NoMethodError)

Developer.create_backend
p Developer.backend
# "Born from the ashes!"

This shows how define_method was used to create an instance method without using a def. However, there’s much more we can do with them. Let’s take a look at this code snippet:

class Developer

  def coding_frontend
    p "writing frontend"
  end

  def coding_backend
    p "writing backend"
  end

end

developer = Developer.new

developer.coding_frontend
# "writing frontend"

developer.coding_backend
# "writing backend"

This code isn’t DRY, but using define_method we can make it DRY:

class Developer

  ["frontend", "backend"].each do |method|
    define_method "coding_#{method}" do
      p "writing " + method.to_s
    end
  end

end

developer = Developer.new

developer.coding_frontend
# "writing frontend"

developer.coding_backend
# "writing backend"

That’s much better, but still not perfect. If we want to add a new method like coding_debug, we need to update the array. Using method_missing, we can make this fully dynamic:

class Developer

  def method_missing(method, *args, &block)
    return super unless method.to_s =~ /^coding_\w+/

    self.class.send(:define_method, method) do
      p "writing " + method.to_s.gsub(/^coding_/, '')
    end

    send(method, *args, &block)
  end

  def respond_to_missing?(method, include_private = false)
    method.to_s.start_with?("coding_") || super
  end

end

developer = Developer.new

developer.coding_frontend
developer.coding_backend
developer.coding_debug

developer.respond_to?(:coding_debug)
# => true

Calling a method that doesn’t exist will trigger method_missing. Here, we create a new method only when the method name starts with "coding_". Otherwise, we delegate to super so Ruby can correctly report truly missing methods. The method is then defined dynamically using define_method, so subsequent calls go directly to the newly created method.

Since we also implement respond_to_missing?, respond_to? will correctly recognize these dynamically defined methods as well.

That’s it! With this approach, we can create a large number of methods on demand while keeping the code DRY. Since define_method is private to Module, we use send to invoke it.

Metaprogramming and Performance in Modern Ruby

Historically, metaprogramming techniques like method_missing were considered slower than defining explicit methods. And to be fair, that’s still true in many cases, especially if the same method has to be resolved dynamically again and again.

That said, Ruby has come a long way. With Ruby 3.x, improvements like YJIT (Yet Another Just-In-Time compiler) help optimize method calls and reduce some of the overhead that comes with dynamic dispatch. So while metaprogramming still adds some overhead, especially with highly dynamic patterns like method_missing, which are harder for the runtime to optimize, the overall performance story in modern Ruby is much better than it used to be.

Still, a little caution goes a long way:

  • Methods defined with define_method (especially when created once and reused) are usually faster than relying on method_missing every time.
  • method_missing shines when you truly need dynamic behavior, such as for flexible APIs.
  • For performance-critical code, plain old method definitions are still your best friend.

A common pattern (and the one we used above) is to combine the two. Let method_missing handle the method the first time it’s called, then define it dynamically so future calls go straight to it. You get the flexibility of metaprogramming without doing the same work over and over again.

And that’s really the idea: metaprogramming is powerful, but like most powerful tools, it works best when you use it with a bit of intention.

Wrapping up

If you want to become a Ruby Jedi, this is just the tip of the iceberg. After you master these building blocks of metaprogramming and truly understand its essence, you can move on to more complex ideas, like creating your own Domain-specific Language (DSL). DSL is a topic in itself, but these basic concepts are a prerequisite to understanding advanced topics. Some of the most widely used gems in Rails were built this way, and you’ve probably used their DSLs without even realizing it, such as RSpec and ActiveRecord.

Hopefully, this article can get you one step closer to understanding metaprogramming and maybe even building your own DSL, which you can use to code more efficiently.

Hire a Toptal expert on this topic.
Hire Now
Nikola Todorovic

Nikola Todorovic

19 Years of Experience

Belgrade, Serbia

Member since August 4, 2015

About the author

Nikola has an MCE degree and almost a decade of experience in software development. His passions are Ruby on Rails and startups.

authors are vetted experts in their fields and write on topics in which they have demonstrated experience. All of our content is peer reviewed and validated by Toptal experts in the same field.
Previous Role
Senior Back-end Ruby Developer

World-class articles, delivered weekly.

By entering your email, you are agreeing to our privacy policy.

World-class articles, delivered weekly.

By entering your email, you are agreeing to our privacy policy.

Join the Toptal® community.