Understanding the Ruby Object Model In Depth
Tags: ruby
Published: 2022-06-22 17:54:42

*This article was originally written by [Abiodun Olowode](https://www.honeybadger.io/blog/authors/abiodunadukeajibade/) on the [Honeybadger Developer Blog](https://www.honeybadger.io/blog/ruby-object-model/).*

According to Wikipedia, object-oriented programming (OOP) is a programming paradigm based on the concept of "objects", which can contain data and code: data in the form of fields (often known as attributes or properties) and code in the form of procedures (often known as methods).

Ruby is a pure object-oriented language, which means that in the Ruby language, everything is an object. These objects, regardless of whether they are strings, numbers, classes, modules, etc., operate in a system called _The Object Model_.

Ruby offers a method called the `object_id`, which is available to all objects. This identifier returns an integer and is never the same for any two objects. Let's get into irb to get our hands dirty; you can do this by typing `irb` in your terminal.

![Object Ids for Objects](https://www.honeybadger.io/images/blog/posts/ruby-object-model/object-id.png)

As seen above, strings, integers, arrays, classes, and even methods are all objects, as they possess an object ID.

In this article, we'll cover the following concepts in detail:

- Classes and instances
- Inheritance
- Public, private, and protected methods
- Mixins
- Modules
- Object hierarchy

## Classes and Instances

In Ruby, classes are where the attributes and attitudes(actions) of an object are defined. If an object is round (an attribute) and should be able to speak (an action), we can tell from the class it belongs to because these attributes and actions are defined as something called _methods_ in that class.
An object belonging to a class is called an _instance_ of that class and is created (instantiated) using `.new`. Let's start by creating a class called `Human`. I suppose we're all humans, so this should be fun.

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

The _initialize_ method identifies the requirements for a new instance of a class to be created. In the case above, we can see that to create a new human, a name is required. Hence, a new instance of human can be created using the command `Human.new(name)`, where the name is whatever you choose. In our case, let's use 'Henry'. To test this in our irb environment, all we need to do is load the file using the command `load './path_to_filename'` and re-use the command for file re-execution whenever changes are made. In my case, it is `load './Human.rb'` because irb is opened in the folder containing the said file. To run the code without irb, we need to add a `puts` statement before every command so that the results will be visible.

![Instantiating the human class](https://www.honeybadger.io/images/blog/posts/ruby-object-model/new-human.png)

When we try to create a new human without a name, we get an argument error because a name is required. However, when we do it correctly, we see that the human named Henry is created and belongs to the class `Human`. Henry is therefore an instance of the class `Human`.

The `@name` variable is called an instance variable because of the `@` symbol it begins with, which means that this variable can be referenced by any other method within the class, as long as the class instance in question exists. Here, we have created it and set it equal to the name with which the object was initialized.

Let's proceed to defining the traits and actions of any object belonging to this class. Since the created objects are called instances of the class, the methods that define their behavior are called _instance methods_. Humans have a name and certain body parts and can perform certain actions, so let's define them.

```ruby
def name
  @name
end

def no_of_legs
  2
end

def no_of_hands
  2
end

def speak
  'blablabla'
end
```

We have added methods that retrieve the name of the created human, define the number of legs and hands a human has, and give the human the ability to speak. We can call these methods on the class instance using `instance.method_name`, as shown below.

![Call methods on Instance](https://www.honeybadger.io/images/blog/posts/ruby-object-model/instance-methods.png)

What if we change our minds and decide that we want to change the name of our class instance Henry. Ruby has a built-in method with which we can do this, but it can also be done manually. Manually, we can change our name method from being just a getter method that gets the name to a setter method that also sets it to a value if one is provided.

```ruby
def name=(new_name)
  @name = new_name
end
```

Using Ruby’s built-in `attr_accessor` method, we can discard our name method and replace it with the line `attr_accessor :name`:

```ruby
class Human
  attr_accessor :name

  def initialize(name)
    @name = name
  end
# rest of the code
end
```

Regardless of the chosen method, at the end of the day, this is what is obtainable.

![Change instance name](https://www.honeybadger.io/images/blog/posts/ruby-object-model/attr-accessor.png)

All the methods created thus far are called `instance methods` because they can be called on any instance of the class but not the class itself. There is also something called a `class method`, which can be called on the class itself and not on its instances. Class methods are named by prefixing the method name with `self.`. Here’s an example:

```ruby
# within the Human class
def self.introduction
  "I am a human, not an alien!"
end
```

![Class methods](https://www.honeybadger.io/images/blog/posts/ruby-object-model/class-methods.png)

As we can see above, the class method is only available to the class itself and not its instances. In addition, just as instance variables exist, we have class variables, which are prefixed with two `@` symbols. An example is shown below.

```ruby
class Human
  attr_accessor :name
  @@no_cars_bought = 0 #class variable

  def initialize(name)
    @name = name
  end

  def self.no_cars_bought
    @@no_cars_bought
  end

  def buy_car
    @@no_of_cars_bought += 1
    "#{@name} just purchased a car"
  end
end
```

In this example, we added a `buy_car` method to enable every human to purchase a car. We also created a class variable called `@@no_of_cars_bought` that increases by 1 each time a car is bought. Lastly, we created a class method called `no_cars_bought` that fetches the number of cars that have been bought. Let's see how this works:

![Class Variables](https://www.honeybadger.io/images/blog/posts/ruby-object-model/class-variables.png)

All the methods defined so far are known as _public_ methods because they are accessible outside the class. We can also define methods called _private_ methods, which can only be accessed within a class. Let's try an example.

```ruby
# at the bottom of the Human class
  def say_account_number
    "My account number is #{account_number}"
  end

  private

  def account_number
    "1234567890"
  end
```

This yields the following:

![Private methods](https://www.honeybadger.io/images/blog/posts/ruby-object-model/private-methods.png)

When we call `henry.account_number`, we get a "NoMethodError" because `account_number` is a private method that can only be accessed from within the class. When accessed via the `say_account_number` method as we did, there are no errors, as this method exists within the same class as the private method. It is important to note that every instance method after the `private` keyword becomes a private method; hence, private methods should be defined at the bottom of the class after all the public methods.

Have you ever heard of _protected_ methods? Yeah, right! These exist too, but we'll talk about them after we understand the concept of _inheritance_.

## Inheritance

Now that we know about classes and instances, let's proceed to talking about _inheritance_. To properly understand the concept of inheritance, let's create a new class called `Mammal` since humans are mammals. I recall a certain phrase in science class: "All humans are mammals, but not all mammals are humans". I also recall that some of the characteristics of mammals include the presence of hair or fur and a complex brain. Let's put this information in the `Mammal` class.

```ruby
class Mammal
  def has_hair?
    "Most certainly, Yes"
  end

  def has_complex_brain?
    "Well, as a mammal, what do you expect?"
  end
end
```

Do you recall my science class phrase? If so, it is important that we create a relationship that validates this statement. So, what do we do? We permit the human class to inherit the properties of the `Mammal` class by adding `< Mammal` to its class definition.

```ruby
class Human < Mammal
  # all other code
end
```

A class that is inheriting the properties of another is called a _subclass_, and the class it inherits from is called a _superclass_. In our case, `Human` is the subclass and `Mammal` is the superclass. It is important to note at this point that if you are defining all the classes in one file like we are doing right now, the `Mammal` class definition should come before the `Human` class in your file, as we don't want to refer to a variable before it is defined. Let's see what additional features the human has now.

![Inheritance](https://www.honeybadger.io/images/blog/posts/ruby-object-model/mammal-inheritance.png)

As shown above, the human now has access to all the attributes defined in the `Mammal` class. If we remove the inheritance (i.e., we remove `< Mammal` from that line of code) and run the command `henry.class.superclass` , we get "Object" as our response. This tells us that every class when not directly inheriting from another class has its superclass as "Object", which further strengthens the fact that in Ruby, even classes are objects.

![Object Class](https://www.honeybadger.io/images/blog/posts/ruby-object-model/object-class.png)

Now that we know about what superclasses are, this is the perfect time to talk about the keyword _super_. Ruby provides this keyword to enable the reuse and modification of methods that already exist on a superclass. In our `Mammal` superclass, recall that we have a method called `has_hair?`; what if we want to add some additional information specific to humans whenever that method is called? This is where the use of the super keyword comes in. In our `Human` class, we define a method with the same name, `has_hair?`.

```ruby
def has_hair?
  super + ", but humans can be bald at times"
end
```

When the super keyword is called, Ruby looks for that method name in the superclass and returns its result. In the above method, we have added some extra information to the result produced by the superclass' `has_hair?` method.

![Super keyword](https://www.honeybadger.io/images/blog/posts/ruby-object-model/super-keyword.png)

Ruby only supports single class inheritance, which means that you can only inherit class properties from one class. I'm sure you're wondering what would happen if you wanted to add multiple properties not specific to a particular class to your class. Ruby also makes a provision for this in the form of _mixins_, but before we talk about them, let's talk about _protected methods_.

### Protected Methods

Protected methods function like private methods in that they are available to be called within a class and its subclasses. Let's create a protected method within the `Mammal` class.

```ruby
  #at the bottom of the Mammal class
  def body_status
    body
  end

  protected
  def body
    "This body is protected"
  end
```

![Protected Method](https://www.honeybadger.io/images/blog/posts/ruby-object-model/protected.png)

As shown above, we cannot access the `body` method because it is protected. However, we can access it from the `body_status` method, which is another method inside the `Mammal` class. We can also access protected methods from subclasses of the class where it is defined. Let's try this within the `Human` class.

```ruby
# within the Human class
def check_body_status
  "Just a human checking that " + body.downcase
end
```

![Protected Methods 2](https://www.honeybadger.io/images/blog/posts/ruby-object-model/protected2.png)

As shown above, regardless of whether the body method is defined within the `Human` class and protected, the `Human` class can access it because it is a subclass of the `Mammal` class. This subclass access is also possible with private methods; however, this leads to the question of what difference exists between these methods.

Protected methods can be called using [explicit receivers](https://blog.kotlin-academy.com/programmer-dictionary-implicit-receiver-vs-explicit-receiver-da638de31f3c), as in `receiver.protected_method`, as long as the receiver in question is the keyword `self` or in the same class as `self`. However, private methods can only be called using explicit receivers when the explicit receiver is `self`.
Let's edit our code by replacing `body.downcase` with `self.body.downcase` in the `check_body_status` method and changing our private method call to also include `self`.

```ruby
def check_body_status
  "Just a human checking that " + self.body.downcase
  # body method is protected
end

def say_account_number
  "My account number is #{self.account_number}"
  # account_number method is private
end
```

![Self usage](https://www.honeybadger.io/images/blog/posts/ruby-object-model/self.png)

P.S.: Before _Ruby 2.7_, when calling a private method to get some information, using `self` was impossible.

Let's go ahead and replace the word `self` with an object in the same class as `self`. In our case, `self` is `Henry`, who is calling the methods, and `Henry` is an instance of the `Human` class, which inherits from the `Mammal` class.

```ruby
def check_body_status
  non_self = Human.new('NotSelf') #same class as self
  puts "Just #{non_self.name} checking that " + non_self.body.downcase
  # Mammal.new is also in the same class as self
  "Just a human checking that " + Mammal.new.body.downcase
end

def say_account_number
  non_self = Human.new('NotSelf')
  "My account number is #{non_self.account_number}"
end
```

![Protected methods use not-self calls](https://www.honeybadger.io/images/blog/posts/ruby-object-model/not-self.png)

As shown above, it is possible to replace `self` in protected methods with an object of the same class as `self`, but this is impossible with private methods.

## Mixins

Mixins are a set of code defined in a _module_, which, when included in or extended to any class, provides additional capabilities to that class. Several modules can be added to a class, as there is no limit to this, unlike what is obtainable in class inheritance. In light of this information, let's create a module called `Movement` to add movement abilities to the human class.

```ruby
module Movement
  def hop
    "I can hop"
  end

  def swim
    "Ermmmm, I most likely can if I choose"
  end
end
```

The next step would be to include this module in our `Human` class. This is done by adding the phrase `include <module_name>` to the class in question. In our case, this would entail adding `include Movement` to the human class, as shown below.

```ruby
class Human < Mammal
  include Movement
  # rest of the code
end
```

Let's test this code:

![Module added using include](https://www.honeybadger.io/images/blog/posts/ruby-object-model/module-added.png)

As shown above, the methods mixed into the class via the module are only available to the instances of the class and not the class itself. What if we want to make the methods in the module available to the class and not the instance? To do this, we replace the "include" term with "extend", as in `extend Movement`.

![Module added using extend](https://www.honeybadger.io/images/blog/posts/ruby-object-model/extend.png)

The `.extend` term can also be used on class instances but in a very different manner. Let's create another module called `Parent`.

```ruby
module Parent
  def has_kids?
    "most definitely"
  end
end
```

![Extend for class instances](https://www.honeybadger.io/images/blog/posts/ruby-object-model/extend2.png)

As shown above, using `.extend(Parent)` on the dad variable makes the `has_kids?` method available to it. This is especially useful when we don't want the module's methods mixed into every class instance. Thus, `extend` can be used only on the particular instance in which we're interested.

## Modules

Modules are housing for classes, methods, constants, and even other modules. Aside from being used as mixins, as seen earlier, modules have other uses. They are a great way to organize your code to prevent names from clashing, as they offer a benefit called _namespacing_.
In Ruby, every class is a module, but no module is a class, as modules can neither be instantiated nor inherited from.
To understand namespacing, let's create a module containing a constant, a class, a method, and another module.

```ruby
module Male
  AGE = "Above 0"

  class Adult
    def initialize
      puts "I am an adult male"
    end
  end

  def self.hungry
    puts "There's no such thing as male food, I just eat."
  end

  module Grown
    def self.age
      puts "18 and above"
    end
  end
end
```

You might have noticed that the methods defined and called within the modules above are prefixed with `self.`. This is because modules cannot be instantiated, and any method defined within them without the `self.` prefix will only be available as a mixin, as we saw earlier when we discussed `mixins`. To call a method on a module itself, we must identify this on the method name via the use of `self.method_name`. Recall that we also used the `self` keyword in class methods; the same principle applies here.

![Modules for namespacing](https://www.honeybadger.io/images/blog/posts/ruby-object-model/module1.png)

As shown above, to reach the classes, modules, or constants defined within a module, we use `module_name::target_name`. To properly understand the namespacing concept, we'll create another module containing a class named `Adult`, and then we'll see how both classes are differentiated.

```ruby
module Female
  class Adult
    def initialize
      puts "I am an adult female"
    end
  end
    def self.hungry
      puts "Maybe there's such a thing as female food, I'm not sure. I just need to eat"
    end
end
```

![Module for namespacing](https://www.honeybadger.io/images/blog/posts/ruby-object-model/module2.png)

As shown above, we have two classes bearing the name "Adult". By wrapping them within their own modules, we're able to use these names without any confusion about the exact adult class being called each time. Furthermore, our code is more readable and more organized, and we implement the [separation of concerns](https://en.wikipedia.org/wiki/Separation_of_concerns) design principle, as we separate different code blocks into several categories based on their functions.

## Object Hierarchy

It's amazing to understand classes, instances, modules, and the concept of inheritance, but knowledge in this area is incomplete without understanding the object hierarchy in Ruby. This refers to the order in which a method is searched for in Ruby. A few methods exist to help us understand this hierarchy; one of them is the `ancestors` method. This method is not available to class instances but can be called on the classes themselves and their ancestors. An example is shown below.

![checking ancestors](https://www.honeybadger.io/images/blog/posts/ruby-object-model/ancestors.png)

From the result of `Human.ancestors`, we see that this method returns the class in question, its directly included modules, its parent class, and the parent's class' directly included modules; this loop continues until it gets to `Basic Object`, which is the root of all objects.

Another available method to get more information about a class is the method `included_modules`.

![Included_modules method](https://www.honeybadger.io/images/blog/posts/ruby-object-model/included-modules.png)

As shown above, the human class has two modules included in it: the one which we directly included using `include Movement` and that which is included in the Object class. This means that each class inherits class properties from its ancestor classes, and each module included in them gets included in it.
Based on this information, we'll carry out a simple exercise to confirm the method lookup path in Ruby and what classes have priorities over others in this path. We'll define methods with the same name but different output strings and place them in the `Human` class, `Movement` module, and `Mammal` class.

```ruby
# in the mammal class
def find_path
  "Found me in the Mammal class path"
end

# in the Movement module
def find_path
  "Found me in the Movement module path"
end
# in the Human class
def find_path
  "Found me in the Human class path"
end
```

Now let's carry out this exercise.

![Method lookup path](https://www.honeybadger.io/images/blog/posts/ruby-object-model/lookup-path.png)

As shown above, the order in which the ancestors are laid out when we call `.ancestors` on a class is the path followed when looking for a method called on an instance of that class. For the `Human` class instance we created, Ruby first searches for the method in the class itself; if not found, it proceeds to any directly included modules; if not found, it proceeds to the superclass and the cycle continues until it gets to the `BasicObject`. If the method is still not found there, a "NoMethodError" is returned. By using the `.ancestors` method, we're can identify the lookup path for an object and where its methods emanate from at any point in time.

Ruby also offers a method called `methods`, by which we can identify all the methods available to any object. We can even carry out subtractions to show which come from its parent and which are peculiar to it. An example is shown below.

![Using .methods](https://www.honeybadger.io/images/blog/posts/ruby-object-model/methods.png)

As shown above, the variable _Henry_ has a lot of methods available to it. However, when we subtract them from those available to the _Object_ class, we find that we're left with only those we specifically defined in our file, and every other method is inherited. This is same for every object, its class, and any of its ancestors. Feel free to get your hands dirty by trying out several combinations involving _Human.methods_, _Mammal.methods_, _Module.methods_, and every other class or module defined earlier; you will find that this gives you a stronger grasp of the Ruby object model.