Include, Extend, And Prepend In Ruby (2024)

This month, I took the time to go back to basics and try to understand how include, extend and prepend work in ruby.

Modules

Ruby uses modules to share behaviour across classes. A module will contain all the logic for the desired behaviour. Any class which would like to use the same behaviour, can either include or extend the module.

What is the difference between include and extend? When a class include’s a module, it adds the module methods as instance methods on the class.

When a class extend’s a module, it adds the module methods as class methods on the class.

12345678910111213141516171819
module A def hello "world" endendclass Foo include Aendclass Bar extend AendFoo.new.hello #worksFoo.hello #errorBar.new.hello #errorBar.hello #works

If it makes sense for an instance of a class to implement the behaviour, then you would include the module. Then each instance has access to the module methods.

If the behaviour is not tied to a particular instance, then you can extend the module. Then the methods will be available as class methods.

self.included

What if you want some methods to be instance methods and others to be class methods? A common way to implement this is to use the self.included callback. Whenever a class includes a module, it runs the self.included callback on the module. We can add the logic for extending another module on the class inside of the self.included method.

To do this, we create a nested module that contains the class methods. The self.included callback will extend the nested module on every class that includes the main module. Then the class will have access to the nested module’s methods as class methods.

12345678910111213141516171819202122232425
module A def self.included(base) base.extend(ClassMethods) end def hello "world" end module ClassMethods def hi "bye" end endendclass Foo include AendFoo.new.hello #worksFoo.hello #errorFoo.new.hi #errorFoo.hi #works

Using self.included, lets us provide both instance and class methods when the module is included.

Note that this approach only works with the module that is included in a class. If we were to extend the module in this example, then Foo would have hello as a class method but not hi.

12345678910111213141516171819202122232425
module A def self.included(base) base.extend(ClassMethods) end def hello "world" end module ClassMethods def hi "bye" end endendclass Foo extend AendFoo.new.hello #errorFoo.hello #worksFoo.new.hi #errorFoo.hi #error

Ancestor chain

So what’s actually happening when you include or extend a module?When you include a module, you add it to the ancestor chain of the class.The ancestor chain is the order of lookup Ruby follows when determining if a method is defined on an object. When you call a method on a class, ruby will check to see if the method is defined on the first item in the ancestor chain (the class). If it is not, it will check the next item in the ancestor chain and so on.

1234567891011
module A def hello "world" endendclass Foo include AendFoo.ancestors # [Foo, A, Object, Kernel, BasicObject]

Similarly, if you extend a module, you add the module to the ancestor list of the singleton class. If you’re unfamiliar with singleton classes, I mention them in my post on singleton methods in ruby. The main idea is that every object has a hidden singleton class which stores methods implemented only on that object. A class object also has a singleton class that stores methods implemented on that class ie class methods.

When calling a class method, ruby will look at the singleton classes ancestor chain to see where the class method is defined. Since class methods get defined on the singleton class, extending a module adds it to the singleton class’s ancestor chain.

123456789101112
module A def hello "world" endendclass Bar extend AendBar.ancestors # [Bar, Object, Kernel, BasicObject]Bar.singleton_class.ancestors # [#<Class:Bar>, A, #<Class:Object>, #<Class:BasicObject>, Class, Module, Object, Kernel, BasicObject]

Prepend

Prepend is like include in its functionality. The only difference is where in the ancestor chain the module is added. With include, the module is added after the class in the ancestor chain. With prepend, the module is added before the class in the ancestor chain. This means ruby will look at the module to see if an instance method is defined before checking if it is defined in the class.

This is useful if you want to wrap some logic around your methods.

123456789101112131415161718
Module A def hello put "Log hello in module" super endendclass Foo include A def hello "World" endendFoo.new.hello# log hello from module# World

Resources

Include, Extend, And Prepend In Ruby (2024)

References

Top Articles
Latest Posts
Article information

Author: Saturnina Altenwerth DVM

Last Updated:

Views: 6363

Rating: 4.3 / 5 (64 voted)

Reviews: 87% of readers found this page helpful

Author information

Name: Saturnina Altenwerth DVM

Birthday: 1992-08-21

Address: Apt. 237 662 Haag Mills, East Verenaport, MO 57071-5493

Phone: +331850833384

Job: District Real-Estate Architect

Hobby: Skateboarding, Taxidermy, Air sports, Painting, Knife making, Letterboxing, Inline skating

Introduction: My name is Saturnina Altenwerth DVM, I am a witty, perfect, combative, beautiful, determined, fancy, determined person who loves writing and wants to share my knowledge and understanding with you.