Up the Ruby Ancestor Chain with method_missing
Tags: ruby, learning
Published: 2019-05-08 10:50:30

---
title: Up the Ruby Ancestor Chain with method_missing
published: true
description: In this post we'll follow a method and travel all the way up the ancestor chain 
cover_image: https://blog.appsignal.com/images/blog/2019-05/ancestor-chain.jpg
canonical_url: https://blog.appsignal.com/2019/05/07/method-missing.html
tags: #ruby, #learning
---

Clutch your carry-on luggage, because today we'll travel all the way up the ancestor chain. We'll follow a method call and see how it goes up the chain as well as find out what happens if the method is missing. And because we love to play with fire, we won't stop there but continue on to ~~playing with fire~~ overriding the `BasicObject#method_missing`. If you pay attention, we might also use it in a practical example. No guarantees though. Let's go!

## The Ancestor Chain

Let's start with the fundamental rules of ancestor chains in Ruby:

- Ruby only supports single inheritance
- It also allows an object to include a set of modules

In Ruby, the ancestor chain is composed of the traversal of all the inherited classes and modules for a given class.

Let’s have a look at an example to show you how the ancestor chain is handled in Ruby.

```ruby
module Auth end

module Session end

module Iterable end

class Collection
  prepend Iterable
end

class Users < Collection
  prepend Session
  include Auth
end

p Users.ancestors
```

produces:

```ruby
[
  Session, Users, Auth,        # Users
  Iterable, Collection,        # Collection
  Object, Kernel, BasicObject  # Ruby Object Model
]
```

First, we call the `ancestors` class method to access the ancestor chain of a given class.

We can see that a call to `Users.ancestors` returns an array of classes and modules that contain, in order:

- The prepended modules of `Users`
- The `Users` class
- The `Users` class’ included modules
- The `Collection` class’ prepended modules — as the direct parent of Users
- The `Collection` class
- The `Collection` class’ included modules — none
- The `Object` class — the default inheritance of any class
- The `Kernel` module — included in `Object` and holding core methods
- The `BasicObject` class — the root class in Ruby

So, the order of appearance for a given traversed class or module is always as follows:

- The prepended modules
- The class or module
- Its included modules

The ancestor chain is mainly traversed by Ruby when a method is invoked on an object or a class.

## The Method Lookup Path

When a message is sent, Ruby traverses the ancestor chain of the message receiver and checks if any of them responds to the given message.

If a given class or module of the ancestor chain responds to the message, then the method associated with this message is executed and the ancestor chain traversal is stopped.

```ruby
class Collection < Array
end

Collection.ancestors # => [Collection, Array, Enumerable, Object, Kernel, BasicObject]

collection = Collection.new([:a, :b, :c])

collection.each_with_index # => :a
```

Here, the `collection.each_with_index` message is received by the Enumerable module. Then the `Enumerable#each_with_index` method is called for this message.

Here, when `collection.each_with_index` is called, Ruby checks if:

- Collection responds to the `each_with_index message` => **NO**
- Array responds to the `each_with_index message` => **NO**
- Enumerable responds to the `each_with_index message` => **YES**

So, from here, Ruby stops the ancestor chain traversal and calls the method associated with this message. In our case, the `Enumerable#each_with_index` method.

In Ruby, this mechanism is called the *Method Lookup Path*.

*Now, what happens if none of the classes and modules that compose a given receiver’s ancestor chain respond to the message?*

## `BasicObject#method_missing`

Enough with playing nice! Let's break things, developer style: by throwing exceptions. We'll implement a `Collection` class and call an unknown method on one of its instances.

```ruby
class Collection
end

c = Collection.new
c.search('item1') # => NoMethodError: undefined method `search` for #<Collection:0x123456890>
```

Here, the `Collection` class doesn't implement a `search` method. So a `NoMethodError` is raised. But where does this error raising comes from?

The error is raised in the `BasicObject#method_missing` method. This method is called when the *Method Lookup Path* ends up not finding any method corresponding to a given message.

Okay... but this method only raises a `NoMethodError`. So it would be great to be able to override the method in the context of our `Collection` class.

## Overriding the `BasicObject#method_missing` Method

Guess What? It’s totally fine to override `method_missing` as this method is also subject to the mechanism of *Method Lookup Path*. The only difference with a normal method is that we’re sure that this method will be found at least once by the *Method Lookup Path*.

Indeed, the `BasicObject class` — which is the root class of any class in Ruby — defines a minimal version of this method. Classic Ruby Magic, n'est pas?

So let’s override this method in our `Collection class`:

```ruby
class Collection
  def initialize
    @collection = {}
  end

  def method_missing(method_id, *args)
    if method_id[-1] == '='
      key = method_id[0..-2]
      @collection[key.to_sym] = args.first
    else
      @collection[method_id]
    end
  end
end

collection = Collection.new
collection.obj1 = 'value1'
collection.obj2 = 'value2'

collection.obj1 # => 'value1'
collection.obj2 # => 'value2'
```

Here, the `Collection#method_missing` acts as a delegator to the `@collection` instance variable. Actually, this is the way Ruby roughly handles object delegation — c.f: the `delegate` library.

If the missing method is a setter method (`collection.obj1 = 'value1'`), then the method name (`:obj1`) is used as the key and the argument (`'value1'`) as the value of the `@collection` hash entry (`@collection[:obj1] = 'value1'`).

## An HTML Tag Generator

Now that we know how the `method_missing` method works behind the scenes, let’s implement a reproducible use case.

Here, the goal is to define the following DSL:

```ruby
HTML.p    'hello world'             # => <p>hello world</p>
HTML.div  'hello world'             # => <div>hello world</div>
HTML.h1   'hello world'             # => <h1>hello world</h1>
HTML.h2   'hello world'             # => <h2>hello world</h2>
HTML.span 'hello world'             # => <span>hello world</span>
HTML.p    "hello #{HTML.b 'world'}" # => <p>hello <b>world</b></p>
```

To do so, we’re going to implement the `HTML.method_missing` method in order to avoid defining a method for each HTML tag.

First, we define an `HTML` module. Then we define a `method_missing` class method in this module:

```ruby
module HTML
  def HTML.method_missing(method_id, *args, &block)
    "<#{method_id}>#{args.first}</#{method_id}>"
  end
end
```

Our method will simply build an HTML tag using the missing `method_id` — `:div` for a call to `HTML.div`, for example.

Note that class methods are also subject to the Method Lookup Path.

We could enhance our HTML tag generator by:

- using the block argument to handle nested tags
- handling single tags — `<br/>` for example

But note that with a few lines of code, we are able to generate a huge amount of HTML tags.

So, to recap:

`method_missing` is a good entry point to create DSLs where most of the commands will share a set of identified patterns.

## Conclusion

We went all the way up the ancestor chain in Ruby and dove into `BasicObject#method_missing`. `BasicObject#method_missing` is part of the *Ruby Hook Methods*. It is used to interact with objects at some precise moments in their lifecycle. Like any of the other *Ruby Hook Methods*, this hook method has to be used carefully. And by carefully, we mean that it should never modify the behaviors of the *Ruby Object Model*—except when you are playing around with it or writing a blog post on it ;-)

Voilà!

_Guest author Mehdi Farsi is the founder of [www.rubycademy.com](https://www.rubycademy.com) which will offer cool courses to learn Ruby and Ruby on Rails when it launches soon._