extend
This was the tip of the week in the August 26, 2021 Ruby Weekly Newsletter.
Last week, we learned something important about singleton classes: class methods are instance methods on a class’ singleton class. Let’s take a step back though - how is this relevant to extend
?
We already know that include
inserts a module into the class’ ancestors chain right after the class that includes
it and prepend
inserts it right before. Well, extend
also inserts a module into an ancestors chain, but it does this on the ancestors chain of the singleton class of a class (not the class itself):
module ExampleModule ; end
class ExampleClass
extend ExampleModule
end
ExampleClass.ancestors
=> [ExampleClass, Object, Kernel, BasicObject]
ExampleClass.singleton_class.ancestors
=> [#<Class:ExampleClass>, ExampleModule, #<Class:Object>,
#<Class:BasicObject>, Class, Module, Object, Kernel, BasicObject]
The #<Class:ExampleClass>
syntax means it’s the singleton class of ExampleClass
. We can see that ExampleModule
is inserted into ExampleClass
’s singleton class’ ancestors chain.
Let’s combine this with what we’ve learned about singleton classes - instance methods on a class’ singleton class are class methods on that class. So say we had an instance method defined on ExampleModule
:
module ExampleModule
def example_module_instance_method
"This is an instance method defined on ExampleModule"
end
end
If we extend ExampleModule
it will become a class method on ExampleClass
:
class ExampleClass
extend ExampleModule
end
ExampleClass.example_module_instance_method
=> "This is an instance method defined on ExampleModule"
If we had instead used include
on the same ExampleModule
, this would remain an instance method:
class ExampleClass
include ExampleModule
end
ExampleClass.new.example_module_instance_method
=> "This is an instance method defined on ExampleModule"
Nice! We’ve now learned three ways to use code from a module in a class:
include
inserts a module into the class’ ancestors chain right after the class.prepend
inserts a module into the class’ ancestors chain right before the class.extend
inserts a module into the class’ singleton class’ ancestors chain right after the class, meaning essentially all of the module’s instance methods can be accessed as if class methods on the class which extends it.