What Makes Ruby Beautiful: Metaprogramming

alexlion

Alex Lion

Posted on August 21, 2020

What Makes Ruby Beautiful: Metaprogramming

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

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

Here is a Shipment class:

class Shipment

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

  def tracking_code(code)
    @tracking_code = code
  end

end
Enter fullscreen mode Exit fullscreen mode

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

s = Shipment.new 
s.respond_to?(:prepare_for_delivery) 
==> true
Enter fullscreen mode Exit fullscreen mode

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

s = Shipment.new

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

It can be used for any classes.

'hello'.respond_to?(:count)
==> true
'world'.respond_to?(:include)
==> false
Enter fullscreen mode Exit fullscreen mode

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.

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
Enter fullscreen mode Exit fullscreen mode

Message example 2

(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 !

class Shipment
  define_method :cancel do |reason|
    @cancelled = true
    puts reason
  end
end
Enter fullscreen mode Exit fullscreen mode

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

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

# 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
Enter fullscreen mode Exit fullscreen mode

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...
Enter fullscreen mode Exit fullscreen mode

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 and Puppet 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 !

💖 💪 🙅 🚩
alexlion
Alex Lion

Posted on August 21, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related