DEV Community

Cover image for What I didn’t know about Ruby Classes
Olivier Dumas
Olivier Dumas

Posted on • Edited on

What I didn’t know about Ruby Classes

I use to write things in order to learn them so here is a series of short articles about what I didn’t know.

The class Class

The class Class inherits directly from Module, and adds to it all the behavior related to instances. Long story short, Modules are used to add methods to one or several Classes of your app, and Classes are used to managed your objects’ properties.

Alt Text
Ps: This diagram is very very very minimalist and focus on Class, but Module and Object have a lot of other children.

Classes and ancestors

In order to see the the ancestor of a Class, you can use the .superclass method :

>> Class.superclass
=> Module

>> Module.superclass
=> Object
Enter fullscreen mode Exit fullscreen mode

And if you want to see all the superclasses of a particular class, you can use the .ancestors method :

>> Class.ancestors
=> [Class, Module, Object, Kernel, BasicObject]
Enter fullscreen mode Exit fullscreen mode

Here, the array includes all the superclasses of Class and this is what we call the ancestor chain in ruby.

Classes are constants

In Ruby, when you define a new class, you are actually creating an instance of the class Class.

class Foo
end

>> Foo.class
=> Class
Enter fullscreen mode Exit fullscreen mode

For example, here, we have just created a new class named Foo, which is an instance of the class Class and we can access to this instance by using the contant Foo.
Following this logic, we can see that we could have created Foo like a casual object :

Foo = Class.new

>> Foo.class
=> Class
Enter fullscreen mode Exit fullscreen mode

How does Ruby looks for a method ?

class MyExample   
 def say_hello
   puts 'hello world'
 end
end

>> MyExample.ancestors
=> [MyExample, Object, Kernel, BasicObject]

>> MyExample.new.say_hello
=> hello world

>> MyExample.new.say_good_bye
=> NoMethodError
Enter fullscreen mode Exit fullscreen mode

say_hello : When we call the say_hello method on an instance of the MyExample class, Ruby looks through the MyExample class for a method named say_hello, it finds it and return the appropriate result.

say_good_bye : If I call a say_good_bye method, ruby will look into MyExample for this method, it will not find it and then it will goes through the ancestor chain passing through each parent at a time until BasicObject to find a method called say_good_bye.

Because the say_good_bye method doesn’t exist in the MyExample class or any of the ancestors, Ruby will return a NoMethodError.

Include and Extend your Classes

Adding a Module’s code to a Class can be done using the include, extend and prepend methods. Let’s focus on the two firsts as they are much more commons.

Include

As the doc says When a class includes a module, module’s instance methods become available as instance methods of the class.

module RandomModule
  def say_thank_you
    puts 'thank you'  
  end
end

class RandomClass   
 include RandomModule
end

>> RandomClass.new.say_thank_you
=> thank you

>> RandomClass.ancestors
=> [RandomClass, RandomModule, Object, Kernel, BasicObject]
Enter fullscreen mode Exit fullscreen mode

Here you can also see that if we check the ancestor chain of our RandomClass, the RandomModule appears !

As we saw earlier, when we call the module’s method (say_thank_you), Ruby will check in our RandomClass class for the method, it will not find it so Ruby will go through each of the ancestors of the RandomClass class in order to find the method.

Extend

I’ve always found obscure the difference between extend and include, but they are actually very different : extend adds the module’s method as class methods and not instance methods.

module RandomModule
  def say_thank_you
    puts 'thank you'  
  end
end

class RandomClass   
 extend RandomModule
end

>> RandomClass.new.say_thank_you
=> NoMethodError

>> RandomClass.say_thank_you
=> thank you

>> RandomClass.ancestors
=> [RandomClass, Object, Kernel, BasicObject]
Enter fullscreen mode Exit fullscreen mode

As you can see, the module is mixed with our RandomClass, and it’s methods are not accessible at the instance level (RandomClass.new.method) but at the class level (RandomClass.method).

You can even use extend on one particular instance :

module RandomModule
  def say_thank_you
    puts 'thank you'  
  end
end

class RandomClass
end

>> RandomClass.new.say_thank_you
=> NoMethodError

>> new_instance = RandomClass.new
>> new_instance.extend RandomModule
>> new_instance.say_thank_you
=> thank you
Enter fullscreen mode Exit fullscreen mode

Classes are open

It means that you can always add and edit methods of a particular ancestor class. You can do it for classes that you have created yourself or even from classes which are part of Ruby.

class String
  def tell_my_size
    self.size
  end

  def reverse
    self.size
  end
end

>> my_string = 'hello world'
>> my_string.tell_my_size
=> 11

>> my_string.reverse
=> 11
Enter fullscreen mode Exit fullscreen mode

We have added a new method to the String class and we have changed the behavior of the reverse method.
Note that it is of course dangerous to override other Classes this way ! Be very careful by doing so and always prefer to edit the existing classes directly.

The last word

I know there are lots of other concepts around Classes but that’s all for this article, feel free to improve my explanation in the comments, I might add more concepts here in the future 👋

Top comments (0)