What I didn’t know about Ruby Classes
I’ve always had this feeling that while learning Ruby and Rails, there were tons of little notions that I didn’t really understood. I’m used 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 manage your objects’ properties.
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
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.
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
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
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
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
endclass 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.
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
endclass 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
endclass RandomClass
end>> RandomClass.new.say_thank_you
=> NoMethodError>> new_instance = RandomClass.new
>> new_instance.extend RandomModule
>> new_instance.say_thank_you
=> thank you
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
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 👀
👉 I’am also the maker of stanza.dev that helps developers to learn coding concepts faster. Feel free to have a look 👨💻 👩💻
Cheers ✌️
Other stories I’ve written ✍️
What I didn’t know about Ruby Numbers