I have been coding with Ruby on rails & React.js for two years now as a professional developer, but I had this feeling that while learning Ruby and Rails, there were tons of little notions that I didn’t really understood. 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 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.
In order to see the the ancestor of a Class, you can use the .superclass method :
>> Class.superclass => Module >> Module.superclass => Object
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]
Here, the array includes all the superclasses of Class and this is what we call the ancestor chain in ruby.
In Ruby, when you define a new class, you are actually creating an instance of the class Class.
class Foo end >> Foo.class => Class
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
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
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.
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.
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]
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.
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]
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
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
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.
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 👋