Metaprogramming Ruby 2
Published: 2021-12-30 04:14:58

Metaprogramming enables you to produce elegant, clean, and beautiful programs as well as their exact opposite programs. This book will teach you the powerful metaprogramming concepts in Ruby, and how to use them judiciously.


![Metaprogramming Ruby](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9i0dzszme4knxut0qogj.jpeg)

In most programming languages, language constructs like variables, classes, methods, etc. are present while you are programming, but disappear before the program runs. They get transformed into byte-code (Java) or CIL (C#), or plain machine-code (C). You can't modify these representations once the program has been compiled. 

In Ruby, however, most language constructs are still there. You can talk to them, query them, manipulate them. This is called introspection. For example, the example below creates an instance of a class and asks for its class and instance variables. 

```ruby
class Language
  def initialize(name, creator)
    @name = name
    @creator = creator
  end
end

ruby = Language.new("Ruby", "Matz")

pp ruby.class
pp ruby.instance_variables
```

**The Object Model**

Objects are first-class citizens in Ruby. You'll see them everywhere. However, objects are part of a larger world that also includes other language constructs, such as classes, modules, and instance variables. All these constructs live together in a system called ***the object model***. 

> Metaprogramming is writing code that writes code. Specifically, Metaprogramming is writing code that manipulates language constructs at runtime, in turn manipulating the Ruby object model. 

**Open Classes**

You can open any class and add new methods on it. For example, let's open String class and add a new method `log` on it. 

```ruby
class String
  def log
    puts ">> #{self}"
  end
end

"Hello World".log		# >> Hello World
```

This is called monkeypatching, and it can cause problems if you re-define existing methods unintentionally. However, if you know what you are doing, monkeypatching can be very powerful. For example, the ActiveSupport gem in Rails makes heavy use of monkeypatching to open Ruby core classes and define new functionality on them. 

**Instance Variables vs. Instance Methods**

An object’s instance variables live in the object itself, and an object’s methods live in the object’s class. 

![Instance Variables vs. Instance Methods](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8b5xkumyfype62km3ht3.png) 

That’s why objects of the same class share methods but don’t share instance variables.

**Classes are Objects**

Everything that applies to objects also applies to classes. Each class is also a module with three additional instance methods: `new`, `allocate`, and `superclass`. 

```ruby
class MyClass
  def my_method
    @v = 1
  end
end

puts MyClass.class  # Class
puts MyClass.superclass  # Object
puts Class.superclass  # Module
puts Object.class  # Class
puts Object.superclass  # BasicObject
pp BasicObject.superclass  # nil
```

**What happens when you call a method?**

1. Finds the method using *method lookup*. For this, Ruby interpreter looks into the *receiver*'s class, including the *ancestor chain*.
2. Execute the method using `self`. 

The *receiver* is the object that you call a method on, e.g. in the statement `myObj.perform()`, `myObj` is the receiver. 

The *ancestor chain* is the path of classes from a class to its superclass, until you reach the root, i.e. `BasicObject`. 

![Inheritance Chain](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ahhmuv0ip5lf1l5ndw3b.jpeg)

**The Kernel**

The `Object` class includes `Kernel` module. Hence the methods defined in `Kernel` are available to every object. In addition, each line in Ruby is executed inside a `main` object. Hence you can call the `Kernel` methods such as `puts` from everywhere. 

If you add a method to `Kernel`, it will be available to all objects, and you can call that method from anywhere.

```ruby
module Kernel
  def log(input)
    puts "Logging `#{input}` from #{self.inspect}"
  end
end

# Logging `hello` from main
log "hello" 

# Logging `a` from "hello"
"hello".log("a") 

# Logging `temp` from String
String.log("temp") 
```

 **The self Keyword**

The Ruby interpreter executes each and every line inside an object - the `self` object. Here are some important rules regarding `self`.

- `self` is constantly changing as a program executes. 
- Only one object can be `self ` at a given time. 
- When you call a method, the receiver becomes `self`. 
- All instance variables are instance variables of `self`, and all methods without an explicit receiver are called on `self`. 
- As soon as you call a method on another object, that other object (receiver) becomes `self`. 

At the top level, `self` is `main`, which is an `Object`. As soon as a Ruby program starts, the Ruby interpreter creates an object called `main` and all subsequent code is executed in the context of this object. This context is also called top-level context. 

```ruby
puts self  # main
puts self.class  # class
```

In a class or module definition, the role of `self` is taken by the class or module itself. 

```ruby
puts self  # main

class Language
  puts self  # Language

  def compile
    puts self  # #<Language:0x00007fc7c191c9f0>
  end
end

ruby = Language.new
ruby.compile
```

**Defining Classes and Methods Dynamically**

```ruby
Language = Class.new do
  define_method :interpret do
    puts "Interpreting the code"
  end
end

# Interpreting the code
Language.new.interpret
```

**Calling Methods Dynamically**

When you call a method, you're actually sending a message to an object. 

```ruby
my_obj.my_method(arg)
```

Ruby provides an alternate syntax to call a method dynamically, using the `send` method. This is called ***dynamic dispatch***, and it's a powerful technique as you can wait until the last moment to decide which method to call, while the code is running. 

```ruby
my_obj.send(:my_method, arg)
```

**Missing Methods**

When you call a method on an object, the Ruby interpreter goes into the object's class and looks for the instance method. If it can't find the method there, it searches up the ancestor chain of that class, until it reaches `BasicObject`. If it doesn't find the method anywhere, it calls a method named `method_missing` on the original receiver, i.e. the object. 

The `method_missing` method is originally defined on the `BasicObject` class. However, you can override it in your class to intercept and handle the unknown methods. 

```ruby
class Language
  def interpret
    puts "Interpreting"
  end

  def method_missing(name, *args)
    puts "Method #{name} doesn't exist on #{self.class}"
  end
end

ruby = Language.new
ruby.interpret # Interpreting
ruby.compile # Method compile doesn't exist on Language
```

**instance_eval**

This `BasicObject#instance_eval` method evaluates a block in the context of an object. 

```ruby
class Language
  def initialize(name)
    @name = name
  end

  def interpret
    puts "Interpreting the code"
  end
end

puts "***instance_eval with object***"

ruby = Language.new "Ruby"

ruby.instance_eval do
  puts "self: #{self}"
  puts "instance variable @name: #{@name}"
  interpret
end

puts "\n***instance_eval with class***"

Language.instance_eval do
  puts "self: #{self}"

  def compile
    puts "Compiling the code"
  end

  compile
end

Language.compile
```

The above program produces the following output

```ruby
***instance_eval with object***
self: #<Language:0x00007fc6bb107730>
instance variable @name: Ruby
Interpreting the code

***instance_eval with class***
self: Language
Compiling the code
Compiling the code
```

**Class Definitions**

A Ruby class definition is just regular code that runs. When you use the `class` keyword to create a class, you aren’t just dictating how objects will behave in the future. You are actually running code.

```ruby
class MyClass
  puts "Hello from MyClass"
  puts self
end

# Output
# Hello from MyClass
# MyClass
```

**class_eval()**

Evaluates a block in the context of an existing class. This allows you to reopen the class and define additional behavior on it. 

```ruby
class MyClass
end

MyClass.class_eval do
  def my_method
    puts "#{self}"
  end
end

MyClass.new.my_method  # #<MyClass:0x00007f945e110b80>
```

A benefit of `class_eval` is that it will fail if the class doesn't already exist. This prevents you from creating new classes accidentally. 

**Singleton Methods and Classes** 

You can define methods on individual objects, instead of defining them in the object's class.

```ruby
animal = "cat"

def animal.speak
  puts self
end

animal.speak  # cat
```

When you define the singleton method on the `animal` object, Ruby does the following:
1. Create a new anonymous class, also called a singleton/eigenclass. 
2. Define the `speak` method on that class. 
3. Make this new class the class of the `animal` object. 
4. Make the original class of the object (String), the superclass of the singleton class.

`animal -> Singleton -> String -> Object`

Classes are objects, and class names are just constants. Calling a method on a class is the same as calling a method on an object. 

> The superclass of the singleton class of an object is the object’s class. The superclass of the singleton class of a class is the singleton class of the class’s superclass.

You can define attributes on a class as follows:

```ruby
class Foo
  class << self
    attr_accessor :bar
  end
end

Foo.bar = "It works"
puts Foo.bar
```

Remember that an attribute is just a pair of methods. If you define an attribute on the singleton class, they become class methods. 

---

**Takeaway**

Though metaprogramming in Ruby looks like magic, it's still just programming. It is so deeply ingrained in Ruby that you can barely write idiomatic Ruby without using a few metaprogramming techniques. 

Ruby expects that you will change the object model, reopen classes, define methods dynamically, and create/execute code on-the-fly. 

Writing perfect metaprogramming code up-front can be hard, so it’s generally easy to evolve your code as you go.

> Keep your code as simple as possible, and add complexity as you need it. 

When you start, strive to make your code correct in the general cases, and simple enough that you can add more special cases later. 

---

_This post was originally published on my [blog](https://akshaykhot.com/books/metaprogramming-ruby/), where you can find my [notes](https://akshaykhot.com/books/) for other books I read. Hope you found it useful._

_Let me know in the comments if you found any mistakes. I look forward to your feedback._