# Ruby Metaprogramming: How to Write Dynamic Code

Ruby is especially loved for its fantastic support for **metaprogramming **, a creative way of writing code that can understand, change, and even create other pieces of code while running.

This exciting feature makes it possible to build very flexible libraries and develop intricate Domain-Specific Languages (DSLs). It's this lively, adaptable trait that really brings frameworks like Ruby on Rails to life.

In this tutorial, you'll develop a basic understanding of Ruby's metaprogramming features, helping you write more dynamic code and better understand the libraries you use every day.

### Prerequisites

Assuming you have Ruby installed on your system, this guide offers examples compatible with Ruby 2.7 and newer versions. To follow along, you should have a solid understanding of Ruby syntax, including classes, objects, instance variables, and methods. Familiarity with object-oriented principles is necessary.

## Getting Started with Metaprogramming

Exploring metaprogramming starts with a fundamental concept in Ruby: **classes are always open**. This means you can reopen any existing class, even built-in ones like `Integer` or `Hash`, and add or modify its methods. This technique is often referred to as "monkey patching."


Start by creating a project directory and navigating into it:

```command
mkdir ruby-metaprogramming-tutorial && cd ruby-metaprogramming-tutorial
```

Then create a file named `app.rb` and add the following code:


```ruby
[label app.rb]
# Reopening the Integer class to add a new helper method
class Integer
  def minutes
    self * 60
  end
end

time_in_seconds = 5.minutes
puts "5 minutes is equal to #{time_in_seconds} seconds."

# You can even chain them
total_time = 2.minutes + 45
puts "Total time is #{total_time} seconds."
```

The code reopens Ruby's `Integer` class to add a `minutes` method. This new method provides a highly readable way to convert a number into seconds. Because the class is open, this functionality is immediately available on every integer in the program, demonstrating how easily you can extend Ruby's core behavior.

Run the file from your terminal:

```command
ruby app.rb
```

```text
5 minutes is equal to 300 seconds.
Total time is 165 seconds.
```

The output confirms our new method works as intended. While powerful, monkey patching should be used thoughtfully, as modifying core classes can sometimes lead to unexpected behavior in larger projects.


## Dynamic method definition

Now that you understand how classes remain open in Ruby, let's explore another powerful metaprogramming feature: **dynamic method definition**. This allows you to create methods programmatically using code, rather than defining them explicitly one by one.

Ruby provides several ways to define methods dynamically, with `define_method` being one of the most commonly used approaches. This technique becomes invaluable when you need to create multiple similar methods or generate methods based on data.

Replace the previous addition to your `app.rb` file with this corrected version:

```ruby
[label app.rb]
# Previous code...

[highlight]
# Dynamic method definition example
module TimeConverter
  def self.included(base)
    # Define multiple conversion methods dynamically when module is included
    %w[seconds minutes hours days].each do |unit|
      base.define_method("to_#{unit}") do
        case unit
        when 'seconds'
          self
        when 'minutes'  
          self / 60.0
        when 'hours'
          self / 3600.0
        when 'days'
          self / 86400.0
        end
      end
    end
  end
end

# Extend Integer to use our TimeConverter methods
class Integer
  include TimeConverter
end

# Test our dynamically created methods
duration = 7200
puts "#{duration} seconds equals:"
puts "- #{duration.to_minutes} minutes"
puts "- #{duration.to_hours} hours" 
puts "- #{duration.to_days} days"
[/highlight]
```

This corrected example uses a **module** instead of a class, which is the proper approach when extending existing classes with `include`. The `self.included` hook automatically runs when the module is included, dynamically creating the conversion methods on the target class.

Run the updated file:

```command
ruby app.rb
```

Now you should see the expected output without any errors:

```text
[output]
5 minutes is equal to 300 seconds.
Total time is 165 seconds.
7200 seconds equals:
- 120.0 minutes
- 2.0 hours
- 0.08333333333333333 days
```

This demonstrates how `define_method` can generate multiple conversion methods from a single loop, keeping your code DRY and maintainable.



## Method Missing

One of Ruby's most intriguing metaprogramming features is **method_missing**, a special method that gets called whenever an object receives a method call that doesn't exist. This creates opportunities to build incredibly flexible APIs that can respond to virtually any method name.

The `method_missing` hook allows you to intercept undefined method calls and handle them programmatically. This technique is used extensively in popular libraries like ActiveRecord, where you can call methods like `User.find_by_email` even though that specific method was never explicitly defined.

Add the following code to your `app.rb` file:

```ruby
[label app.rb]
# Method missing example
class MathProxy
  def method_missing(method_name, number)
    operation = method_name.to_s
    case operation
    when 'double'
      number * 2
    when 'square'
      number * number
    when 'half'
      number / 2.0
    else
      super
    end
  end
end

# Test our math proxy
math = MathProxy.new
puts math.double(5)
puts math.square(4)
puts math.half(10)
```

This example shows how `method_missing` can create dynamic methods like `double`, `square`, and `half` without explicitly defining them. The proxy intercepts these calls and performs the corresponding mathematical operations.

Run the updated file:

```command
ruby app.rb
```

```text
[output]
10
16
5.0
```

Using method_missing offers great flexibility, but it should be used carefully because it catches all undefined method calls, which can make debugging harder if not implemented properly.


## Dynamic variable access

Ruby's metaprogramming capabilities extend beyond methods to **dynamic variable manipulation**. You can programmatically read, write, and even create instance and class variables at runtime using built-in methods like `instance_variable_get`, `instance_variable_set`, and `class_variable_set`.

This technique proves particularly useful when building configuration systems, debugging tools, or any scenario where variable names are determined dynamically rather than hardcoded.

Clear the contents and add the following code to your `app.rb` file:

```ruby
[label app.rb]
# Dynamic variable access example
class DataStore
  def initialize
    @data = {}
  end
  
  def store(key, value)
    instance_variable_set("@#{key}", value)
    @data[key] = value
  end
  
  def retrieve(key)
    instance_variable_get("@#{key}")
  end
  
  def list_variables
    instance_variables.map { |var| var.to_s.delete('@') }
  end
end

# Test dynamic variables
store = DataStore.new
store.store('username', 'alice')
store.store('age', 25)

puts "Username: #{store.retrieve('username')}"
puts "Age: #{store.retrieve('age')}"
puts "Variables: #{store.list_variables.join(', ')}"
```

This example demonstrates how to dynamically create and access instance variables using `instance_variable_set` and `instance_variable_get`. The `DataStore` class can create variables with any name at runtime, making it incredibly flexible.

Run the updated file:

```command
ruby app.rb
```

```text
[output]
Username: alice
Age: 25
Variables: data, username, age
```

Dynamic variable access opens up powerful possibilities for creating flexible data structures and introspection tools, though it should be used thoughtfully to maintain code readability.


## Evaluating code dynamically

Ruby provides two powerful methods for **dynamic code evaluation**: `class_eval` and `instance_eval`. These methods allow you to execute code in different contexts, giving you the ability to modify classes and objects on the fly by changing where the code runs.

While `class_eval` executes code in the context of a class, `instance_eval` runs code in the context of a specific object. This distinction becomes crucial when you need to add methods or modify behavior dynamically.

Add the following code to your `app.rb` file:

```ruby
[label app.rb]
# Dynamic code evaluation example
class Book
end

# Using class_eval to add methods to the class
Book.class_eval do
  def initialize(title, author)
    @title = title
    @author = author
  end
  
  def info
    "#{@title} by #{@author}"
  end
end

# Using instance_eval to add methods to a specific instance
book = Book.new("1984", "George Orwell")
book.instance_eval do
  def special_note
    "This is a classic!"
  end
end

puts book.info
puts book.special_note
```

This example demonstrates how `class_eval` adds methods available to all instances of the `Book` class, while `instance_eval` adds a method only to the specific `book` object. This flexibility allows for highly targeted modifications.

Run the updated file:

```command
ruby app.rb
```

```text
[output]
1984 by George Orwell
This is a classic!
```

Dynamic code evaluation provides exceptional flexibility for creating configuration systems and extending objects at runtime, though it should be used judiciously as it can make code harder to trace and debug.


## Building a Simple DSL

Now that you've learned the core metaprogramming techniques, let's combine them to create a **Domain-Specific Language (DSL)**. DSLs provide intuitive, readable syntax for specific problem domains, and they're one of the most practical applications of Ruby's metaprogramming features.

A DSL allows you to write code that reads almost like natural language, making complex configurations or workflows much more accessible. Ruby on Rails' routing system and RSpec's testing syntax are famous examples of well-designed DSLs.

Add the following code to your `app.rb` file:

```ruby
[label app.rb]
# Simple DSL for building HTML
class HtmlBuilder
  def initialize
    @content = []
  end
  
  def method_missing(tag, content = nil, &block)
    if block_given?
      nested = HtmlBuilder.new
      nested.instance_eval(&block)
      @content << "<#{tag}>#{nested.to_html}</#{tag}>"
    else
      @content << "<#{tag}>#{content}</#{tag}>"
    end
  end
  
  def to_html
    @content.join("\n")
  end
end

# Using our HTML DSL
builder = HtmlBuilder.new
builder.instance_eval do
  html do
    head do
      title "My Page"
    end
    body do
      h1 "Welcome!"
      p "This HTML was built with Ruby metaprogramming."
    end
  end
end

puts builder.to_html
```

This DSL demonstrates three key metaprogramming concepts working together: `method_missing` catches undefined method calls like `html` and `body`, treating them as HTML tags; `instance_eval` changes the context so methods are called on the builder object; and dynamic method creation happens as each tag method is intercepted and processed. The result is syntax that reads like a natural description of HTML structure.

Run the updated file:

```command
ruby app.rb
```

```text
[output]
"This HTML was built with Ruby metaprogramming."
<html><head><title>My Page</title></head>
<body><h1>Welcome!</h1></body></html>
```

This example shows how combining metaprogramming techniques creates powerful, readable interfaces that transform complex tasks into intuitive, al most English-like code.


## Final thoughts

You've now explored Ruby's core metaprogramming features, from reopening classes to building complete DSLs. These techniques form the foundation of popular Ruby libraries and frameworks, giving you insight into how tools like Rails create their elegant APIs.

Use metaprogramming judiciously since explicit code is often clearer than clever code. When you do use these techniques, include proper documentation and follow Ruby conventions like implementing `respond_to_missing?` alongside `method_missing`.