Understanding Ruby Method Lookup
Tags: ruby
Published: 2021-07-08 17:29:13

*This article was originally written by [Kingsley Silas][1] on the [Honeybadger Developer Blog][2].*  

What do you think happens when you call a method? How does Ruby decide which method to call when there’s another method with the same name? Have you ever wondered where the method is housed or sourced from?

Ruby employs a defined "way" or "pattern" to determine the right method to call and the right time to return a “no method error”, and we can call this "way" the **Ruby Method Lookup Path**.
In this tutorial, we’ll be diving into Ruby’s method lookup. At the end, you’ll have a good understanding of how Ruby goes through the hierarchy of an object to determine which method you’re referring to.

To fully grasp what we'll be learning, you'll need to have a basic understanding of Ruby. While we'll mention things like modules and classes, this will not be a deep dive into what they do. We'll only cover the depth needed to reach the goal of this tutorial: show you how Ruby determines the message (method) you're passing to an object.

## Overview

When you call a method, such as `first_person.valid?`, Ruby has to determine a few things:

1. Where the method `.valid?` is defined.
2. Are there multiple places where the `.valid?` method is defined? If so, which is the right one to use in this context.

The process (or path) Ruby follows in figuring this out is what we call **method lookup**. Ruby has to find where the method was created so that it can call it. It has to search in the following places to ensure it calls the right method:

1. Singleton methods: Ruby provides a way for an object to define its own methods; these methods are only available to that object and cannot be accessed by an instance of the object.
2. Methods in mixed-in modules: Modules can be mixed into a class using `prepend`, `include`, or `extend`. When this happens, the class has access to the methods defined in the modules, and Ruby goes into the modules to search for the method that has been called. It's also important to know that other modules can be mixed into the initial modules, and the search also progresses into these.
3. Instance methods: These are methods defined in the class and accessible by instances of that class.
4. Parent class methods or modules: If the class happens to be a child of another class, Ruby searches in the parent class. The search goes into the parent class singleton methods, mixed modules, and its parent class.
5. Object, Kernel, and BasicObject: These are the last places where Ruby searches. This is because every object in Ruby has these as part of their ancestors.

## Classes and Modules

Methods are often called on objects. These objects are created by certain classes, which could be Ruby's inbuilt classes or classes created by a developer.

```ruby
class Human
  attr_reader :name

  def initialize(name)
    @name = name
  end

  def hello
    put "Hello! #{name}"
  end
end
```

We can then call the `hello` method that we have created above on instances of the `Human` class; for example,

```ruby
john = Human.new("John")
john.hello # Output -> Hello John
```

The `hello` method is an instance method; this is why we can call it on instances of the `Human` class. There might be cases where we do not want the method to be called on instances. In these cases, we want to call the method on the class itself. To achieve this, we'll have to create a class method. Defining a class method for the class we have above will look like this:

```ruby
  def self.me
    puts "I am a class method"
  end
```

We can then call this by doing `Human.me`. As the complexity of our application grows (imagine we're building a new start-up here), there might come a time when two or more of our classes have multiple methods that do the same thing. If this happens, it means we need to keep things dry and make sure that we do not repeat ourselves. The issue involves how we share functionality across these classes.

If you have not used modules before, you might be tempted to create a new class strictly for these "shared" methods. However, doing so might result in negative consequences, especially when you need to utilize multiple inheritance, something that Ruby does not support. Modules are the best means of handling this case. Modules are similar to classes, but they have a few differences. First, here is an example of what a module looks like:

```ruby
module Movement
  def walk
    puts "I can walk!"
  end
end
```

1. Definition begins with the `module` keyword instead of `class`.
2. Modules cannot have instances, so you cannot use `Movement.new`.

## Methods

Methods can be viewed as actions to be performed by a particular object. If I have an array like `[2, 3, 4]` assigned to a variable called `numberList`, the `.push` method is an action that can be performed by the array to **put** the value it receives into the array. This code snippet is an example:

```ruby
john.walk
```

It might be typical of you to say something like, "I'm calling the object's method", in which `john` references an object that is an instance of `Human`, and `walk` is the method. However, this isn't completely true because the inferred method tends to come from the object's class, superclass, or **mixed-in** module.

It is important to add that it's possible to define a method on an object, even an object like `john`, because everything is an object in Ruby, even a class used in creating objects.

```ruby
def john.drip
  puts "My drip is eternal"
end
```

The `drip` method can only be accessible by the object assigned to `john`. `drip` is a singleton method that will be available to the `john` object. It is important to know that there's no difference between singleton methods and class methods, as you can see from this [Stack Overflow answer](https://stackoverflow.com/a/34429881/2119604). Unless you're referring to a method defined on an object like in the example above, it would be incorrect to say that the method belongs to a certain object. In our example, the `walk` method belongs to the `Movement` module, while the `hello` method belongs to the `Human` class. With this understanding, it will be easier to take this a step further, which is that to determine the exact method that is being called on an object, Ruby has to check the object's class or super class or modules that have been **mixed** in the object's hierarchy.

## Mixing Modules

Ruby supports single inheritance only; a class can only inherit from one class. This makes it possible for the child class to inherit the behavior (methods) of another class. What happens when you have behaviors that need to be shared across different classes? For example, to make the `walk` method available to instances of the `Human` class, we can **_mix in_** the `Movement` module in the `Human` class. So, a rewrite of the `Human` class using `include` will look like this:

```ruby
require "movement" # Assuming we have the module in a file called movement.rb

class Human
  include Movement

  attr_reader :name

  def initialize(name)
    @name = name
  end

  def hello
    put "Hello! #{name}"
  end
end
```

Now, we can call the `walk` method on the instance:

```ruby
john = Human.new("John")
john.walk
```

### Include

When you make use of the **include** keyword, like we did above, the methods of the included module(s) get added to the class as instance method(s). This is because the **_included_** module is added among the **ancestors** of the `Human` class, such that the `Movement` module can be seen as a parent of the `Human` class. As you can see in the example we shown above, we've called the `walk` method on the instance of the `Human` class.

### Extend

In addition to **include**, Ruby provides us with the **extend** keyword. This makes the method(s) of the module(s) available to the class as class method(s), which are also known as singleton methods, as we learned previously. So, if we have a module called `Feeding` that looks like

```ruby
module Feeding
  def food
    "I make my food :)"
  end
end
```

we can then share this behavior in our `Human` class by requiring it and adding `extend Feeding`. However, to use it, instead of calling the `food` method on the instance of the class, we'll call it on the class itself, the same way we call class methods.

```ruby
Human.food
```

### Prepend

This is similar to **_include_** but with some differences, as stated in this [post](https://medium.com/@leo_hetsch/ruby-modules-include-vs-prepend-vs-extend-f09837a5b073);

> It actually works like include, except that instead of inserting the module between the class and its superclass in the chain, it will insert it at the bottom of the chain, even before the class itself.
>
> What it means is that when calling a method on a class instance, Ruby will look into the module methods before looking into the class.

If we have a module that defines a `hello` method that we then mix into the `Human` class by using `prepend`, Ruby will call the method we have in the module instead of the one we have in the class.

To properly understand how Ruby's `prepend` works, I suggest taking a look at this [article](https://chunksofco.de/rubys-prepend-how-is-it-useful-d3bba8d11a95).

## Method Lookup Path

The first place the Ruby interpreter looks when trying to call a method is the singleton methods. I created this [repl](https://repl.it/@kinsomicrote/method-lookup), which you can play with to see the possible results.

Suppose we have a bunch of modules and classes that look like the following:

```ruby
module One
  def another
    puts "From one module"
  end
end

module Two
  def another
    puts "From two module"
  end
end

module Three
  def another
    puts "From three module"
  end
end

class Creature
  def another
    puts "From creature class"
  end
end
```

Let's go ahead to mix these into the `Human` class.

```ruby
class Human < Creature
  prepend Three
  extend Two
  include One

  def another
    puts "Instance method"
  end

  def self.another
    puts "From Human class singleton"
  end
end
```

Aside from mixing the modules, we have an instance and class method. You can also see that the `Human` class is a subclass of the `Creature` class.

## First Lookup - Singleton Methods

When we run `Human.another`, what gets printed is `From Human class singleton`, which is what we have in the class method. If we comment out the class method and run it again, it will print `From two module` to the console. This comes from the module we mixed in using `extend`. It goes to show that the lookup begins among singleton methods. If we remove (or comment out) `extend Two` and run the command again, this will throw a **_method missing error_**. We get this error because Ruby could not find the `another` method among the singleton methods.

We'll go ahead and make use of the class instance by creating an instance:

```ruby
n = Human.new
```

We'll also create a singleton method for the instance:

```ruby
def n.another
  puts "From n object"
end
```

Now, when we run `n.another`, the version that gets called is the singleton method defined on the `n` object. The reason Ruby won't look call the module mixed in using `extend` in this case is because we're calling the method on the instance of the class. It is important to know that singleton methods have a higher relevance than methods involving modules mixed in using `extend`.

## Second Lookup - Modules Mixed In Using `preprend`

If we comment out the singleton method on the `n` object and run the command, the version of the method that gets called is the module we mixed in using `prepend`. This is because the use of `prepend` inserts the module before the class itself.

## Third Lookup - The Class

If we comment out the module `Three`, the version of the `another` method that gets called is the instance method defined on the class.

## Fourth Lookup - Modules Mixed In Using `include`

The next place Ruby searches for the method is in modules that have been mixed in using `include`. So, when we comment out the instance method, the version we get is that which is in module `One`.

## Fifth Lookup - Parent Class

If the class has a parent class, Ruby searches in the class. The search includes going into the modules mixed into the parent class; if we had the method defined in a module mixed into the `Creature` class, the method will get called.

## The End Of The Method Search

We can know where the search of a method ends by checking its ancestors: calling `.ancestors` on the class. Doing this for the `Human` class will return `[Three, Human, One, Creature, Object, Kernel, BasicObject]`. The search for a method ends at the `BasicObject` class, which is Ruby's root class. Every object that is an instance of some class originated from the `BasicObject` class.

After the method search goes past the developer-defined parent class, it gets to the following:

- the `Object` class
- the `Kernel` module
- the `BasicObject` class

## `method_missing` Method

If you have been using Ruby for a while, you've probably come across `NoMethodError`, which happens when you attempt to an unknown method on an object. This happens after Ruby has gone through the ancestors of the object and could not find the called method. The error message you receive is handled by the [`method_missing`](https://ruby-doc.org/core-2.7.1/BasicObject.html#method-i-method_missing) method, defined in the `BasicObject` class. It is possible to override the method for the object you're calling the method on, which you can learn about by checking [this](https://dev.to/morinoko/dynamic-method-handling-with-methodmissing-and-respondtomissing-in-ruby-and-rails-oac).

## Conclusion

Now you know the path Ruby takes in figuring out the method called on an object. With this understanding, you should be able to easily fix errors arising as a result of calling an unknown method on an object.

## About Honeybadger
[Honeybadger][3] has your back when it counts. We're the only error tracker that combines exception monitoring, uptime monitoring, and cron monitoring into a single, simple to use platform.  

Our mission: to tame production and make you a better, more productive developer. [Learn more][3]


[1]: https://www.honeybadger.io/blog/ruby-method-lookup/#authorDetails
[2]: https://www.honeybadger.io/blog/ruby-method-lookup/
[3]: https://www.honeybadger.io/