📜 ⬆️ ⬇️

We delve into include and extend


Translator's note: before reading this post, I recommend that you first familiarize yourself with the post. Understand the Ruby metaclasses .

All rubists are familiar with the formal definitions for include and extend . You do include a module to add class instance methods, and extend - to add class methods. Unfortunately, these definitions are not entirely accurate. They cannot explain why we use instance. extend (Module) to add methods to the object. Shouldn't we in this case use an instance. include (Module) ? To understand this question, let's start by finding out where the methods are stored.

The methods I keep for you and the guy who keeps my methods


Objects in Ruby do not store their own methods. Instead, they create a singleton class so that it stores their methods.
')
class A def self.who_am_i puts self end def speak_up(input) puts input.upcase end end 

The interpreter will create a class A and a singleton class attached to it (we will refer to the object's singleton class using the prefix ' in front of the object name). Any class instance methods (like speak_up ) are added to methods stored in class A. Class methods (like who_am_i ) are stored in class ' A.

 A.singleton_methods #    'A #=> [:who_am_i] 



The same thing happens with instances of a class. If we have an object of class A and we add a method to it, we cannot store this method inside the object itself. Remember - objects in Ruby do not store their own methods.

 a = A.new def a.not_so_loud(input) puts input.downcase end 

Here again, a singleton class is created for the object " a ", so that it stores the not_so_loud method.



Now we have a method that belongs only to the object " a " and does not affect other objects of class A.

Am I my father?


Class A contains methods and information about the chain of inheritance needed for object " a " and all other objects of class A. Similarly, the singleton class A contains methods and information about the inheritance chain for class A. You can think of class A as an object of class ' A. The trick is that we cannot directly refer to the singleton class ' A. This means that we need to somehow distinguish between adding methods to A and to 'A. Here then include and extend and come into play.

include


When you include a module in an object, you add methods to the object's inheritance chain.

 class A include M end 



This is easily verified by checking the ancestors of class A.

 A.ancestors #=> [A, M, Object, Kernel, BasicObject] 


extend


extend is the same as include , but for a singleton object class.

 class A extend M end 



And again we can confirm this by checking the ancestors of the class ' A.

 A.singleton_class.ancestors #=> [M, Class, Module, Object, Kernel, BasicObject] 

We can also use extend for an object.

 a = A.new a.extend(M) a.singleton_class.ancestors #=> [M, A, Object, Kernel, BasicObject] 



If you think about extend as a way to simply add class methods, then everything we have just done does not make much sense. However, if you look at it as a way to add methods to the singleton class of an object, then the examples above will become clearer.

Hook included


Each include call checks the plug-in for the method included . This method is executed when the module is connected using include . This is like a constructor ( initialize ) for connections. As you may have guessed, extend for this purpose has its own method - extended . So when you want to add both class methods and class instance methods at once, you can use the included hook for this.

 module M def self.included(base) base.extend(ClassMethods) end def speak_up(input) puts input.upcase end module ClassMethods def who_am_i puts self end end end class C include M end c = C.new 

First we include the module M in the inheritance chain of class C.



Then we extend class C by adding methods to the inheritance chain of the class ' C.



Conclusion


When you start digging deeper than the typical use of include and extend , you find something strange and frightening. However, it is necessary to understand the underlying implementation and everything at once makes sense. Let's now give the definition of include and extend again.

include - adds module methods to the object.
extend - calls the include for the singleton class of the object.

If you are interested in even more details about how the interpreter works, then I recommend watching Patrick Farley’s presentation on Ruby Internals .

Translator's note: Another thing, why we cannot use instance.include (Module) is that the include method is a private method of the Module class. In general, before reading this article, I also did not imagine the work of extend and include , so I considered it worthwhile to translate it.

I will bring some more clarity: what is called the “singleton class” (singleton class) in the article has other names: metaclass and eigenclass. This is all the same entity for which there is no “official" name in the Ruby community yet. I used the "singleton class" because it's closer to the original. However, Matz (the creator of the language) is more impressed with the term eigenclass.

Source: https://habr.com/ru/post/143483/


All Articles