What Makes Ruby Beautiful: Metaprogramming
Tags: ruby, rails
Published: 2020-08-21 20:57:02

Ruby is one of the underrated programming languages among modern developers. It has become popular with the **Ruby on Rails** framework.

> Ruby is making developers happy, productive and enjoying programming. - Yukihiro Matsumoto

You are maybe coming from the JS world with a lot of frameworks or from Java and all its complexity.
If you are enough of wasting your time building software that matter and want concrete result as soon as possible, let me introduce you to **Ruby**.

---

I'll introduce you to the concept of **metaprogramming**.
First time I learned Ruby, my **mind literally blew** when I was confronted to this.

> Think about a program that can generate program itself. A program that can generate executable code without the help of a developer.

*No, it's not sci-fi, it's Ruby Metaprogramming !*

If I had to **explain to a 5 years old**, imagine you want to draw a sunny city, you take your pen and write "Sunny City" on the paper. Push the button and magic happens. That's metaprogramming.

Now, I'll explain to real developers.

In Ruby, you can make runtime introspection on method. In another hand, you can ask an object about its capabilities (*Do you have this method?*), its variables, constants and its class and ancestors.

In this article I'll show you some examples with methods like **respond_to?** (? is a Ruby convention for method returning a boolean) **send** and **define_method**. But there is a lot more like **method_missing**, remove_method and **undef_method**.
I'll explain these 3 methods and finally show you the mind-blowing examples.

# The respond_to?() method

This method tests your class if it can handle a specific **message**, for those who don't speak Ruby: it checks if a **method** can be called in a specific class.

![Message example](https://dev-to-uploads.s3.amazonaws.com/i/br56i7j329jc5qz6r3je.png)

(In the Ruby vocabulary, message is known as a method).

Here is a *Shipment* class:

```ruby
class Shipment

  def prepare_for_delivery
    @message = 'Shipment is prepared for delivery'
  end

  def tracking_code(code)
    @tracking_code = code
  end

end
```

Use the respond_to? method to check if prepare_for_delivery method exists:

```ruby
s = Shipment.new 
s.respond_to?(:prepare_for_delivery) 
==> true
```

A more complete example sending a message, if this one exists, to another object.

```ruby
s = Shipment.new

if s.respond_to?(:cancel_shipping)
  s.cancel_shipping
else
  puts "Oh no ! Shipment cannot be cancel."
end
```

It can be used for any classes.

```ruby
'hello'.respond_to?(:count)
==> true
'world'.respond_to?(:include)
==> false
```

**_Did you catch it?_**

Well, keep it in mind for the end of this article.

# The send() method

You can call any method in a class with the **send()** method.

```ruby
s = Shipment.new

if s.respond_to?(:tracking_code)
  s.send(:tracking_code, '123ABC') # or s.send('cancel_shipping', '123ABC')
else
  puts "Tracking code is not available."
end
```

![Message example 2](https://dev-to-uploads.s3.amazonaws.com/i/m5i4v0oyg82ox59eyec5.png)

(Message is sent in the first parameter of send())

I'm hearing you saying: *"Why not calling our method directly ?"*
 
Yes, we can and I know that this example is not a real world example on how we use it.

Let's continue.

# The define_method() method

Now that you know the logic, you could even find the behavior of this method before I explain it to you.

It… defines a method ? *Well done !*

```ruby
class Shipment
  define_method :cancel do |reason|
    @cancelled = true
    puts reason
  end
end
```

You just defined a new method for the **Shipment** class, setting an instance variable **cancelled** to **true** and printing the **reason**.

Wait, for the final example that will blow your mind.

# Metaprogramming in one example

You know the basics of Ruby metaprogramming, let's see the final example.

![Cargo](https://dev-to-uploads.s3.amazonaws.com/i/g6652i0gbuyi5abljyqo.png)

Let's embark on the sea trip to metaprogramming and set sail !

```ruby
# We create a container class. We only store the product's name at instantiation
class Container

  attr :product_name

  def initialize(name)
    @product_name = name
  end

end

# Apples ! Oranges ! All of theses fruits are contained in FruitContainer extending Container
# For simplification we only have one scanner
class FruitContainer < Container

  def apples_scanner
    puts "Scanning apples..."
  end

end

# Potatoes ! Broccoli ! All of vegetables are contained in VegetableContainer extending Container
# For simplification we only have one scanner
class VegetableContainer < Container

  def potatoes_scanner
    puts "Scanning potatoes..."
  end

end

# The Cargo containing all the containers
class Cargo

  # The constructor accepting multiple parameters
  def initialize(*containers)
    containers.each do |container|
      # self.class.send is used to define singleton methods for our class
      # we could also use define_singleton_method
      self.class.send(:define_method, "inspect_#{container.product_name}") do
        scanner_name = "#{container.product_name}_scanner"
        if container.respond_to?(scanner_name)
          container.send(scanner_name)
        else
          puts "No scanner found."
        end
      end
    end
  end

end

potatoes_container = VegetableContainer.new "potatoes"
apples_container = FruitContainer.new "apples"
cargo = Cargo.new(potatoes_container, apples_container)

cargo.inspect_apples
cargo.inspect_potatoes
```

We used all the methods I explained. For each **Container** classes, we define new methods which call a method (if exists) based on their product name, a scanner.

The output:

```
Scanning apples...
Scanning potatoes...
```

---

Now, you know what metaprogramming is and how it works. Well done.

One of the first uses case of metaprogramming is creating its own **DSL** (Domain Specific Languages).

There are some well-known tools based on the Ruby DSL, [Chef](https://www.chef.io/) and [Puppet](https://puppet.com/) for DevOps peoples.

*This is what makes Ruby beautiful.*

#### Since I can't offer you a book on this topic, I'm replying to all your questions and feedback. [Join me on Twitter !](https://twitter.com/lnalx)