DEV Community

abbiecoghlan
abbiecoghlan

Posted on • Edited on

Ruby Modules: include vs extend vs prepend

Previously, we learned about the differences between class inheritance and modules in Ruby. You can read more about that here. In that post, we reviewed how to use modules to share functionality between classes that do not have a clear hierarchical arrangement. Specifically, we used the include keyword to give our classes access to instance methods. In this post, we will focus on digging deeper into modules in Ruby by reviewing the use of the include keyword and introducing two other keywords, extend and prepend. Understanding the different behaviors of each of these three keywords and knowing when to use them will provide us with the power to utilize modules to extend functionality to our classes in a variety of ways.

include

include is the keyword that we will use within our classes to provide access to the methods defined in a module as instance methods in our class. In order to do so, we will first define a module.

module IndoorPet
    def can_be_housebroken?
        true
    end
end  
Enter fullscreen mode Exit fullscreen mode

Now, all we have to do to give our classes access to the instance method can_be_housebroken? as it is defined in our IndoorPet module is add include IndoorPet in the body of our class when we define it.

class Animal 
end 

class Dog < Animal 
    include IndoorPet
end 

class Cat < Animal
    include IndoorPet
end 

class Rabbit < Animal 
    include IndoorPet
end


roger = Rabbit.new("Roger")

roger.can_be_housebroken?
=> true 

Rabbit.can_be_housebroken?
=> NoMethodError: undefined method `can_be_housebroken?' for Rabbit:Class


Enter fullscreen mode Exit fullscreen mode

With that, we have now included the module's instance method can_be_housebroken? in our Dog, Cat, and Rabbit classes. Thus, any instances of those classes will now have access to the can_be_housebroken? method.

When you include a module, Ruby will insert the module into the class's ancestry chain just above our class, between our class and it's superclass. This chain can be seen when calling .ancestors on our Rabbit class.

Rabbit.ancestors
=> [Rabbit, IndoorPet, Animal, Object, PP::ObjectMixin, Kernel, BasicObject]
Enter fullscreen mode Exit fullscreen mode

extend

Now that we know how to use include to provide a class with access to a module's instance methods, you might be wondering how to provide a class with access to a module's class methods. The extend keyword does just that. It is used in exactly the same way as include, except that instead of instance methods which can be called on individual instances of our class, all of the methods we are extending to our class will be provided as class methods we can call on the class as a whole.

Instead of including can_be_housebroken? as instance method, let's try extending it as a class method. First, we define our module.

module IndoorPet
    def can_be_housebroken?
        true
    end
end  
Enter fullscreen mode Exit fullscreen mode

Then we use the extend keyword the same way we used the include keyword previously.

class Animal 
end 

class Dog < Animal 
    extend IndoorPet
end 

class Cat < Animal
    extend IndoorPet
end 

class Rabbit < Animal 
    extend IndoorPet
end



roger = Rabbit.new("Roger")

roger.can_be_housebroken?
=> NoMethodError: undefined method `can_be_housebroken?' for  #<Rabbit:0x00007fa84389c748> 

Rabbit.can_be_housebroken?
=> true

Enter fullscreen mode Exit fullscreen mode

With that, we have now extended the module's class method can_be_housebroken? to our Dog, Cat, and Rabbit classes. Now those classes as a whole have access to the can_be_housebroken? method. Note that you can no longer call can_be_housebroken? on an individual instance of the class. With extend, we have only given the method to the class as a class method.

When you extend a module, ruby will provide the module's methods to the class as class methods. However, unlike with include, when you extend a module, Ruby will not insert the module into the class's ancestry chain. We can see this by calling .ancestors on our Rabbit class.

Rabbit.ancestors
=> [Rabbit, Animal, Object, PP::ObjectMixin, Kernel, BasicObject]
Enter fullscreen mode Exit fullscreen mode

prepend

The third keyword for providing a module's methods to a class is prepend. We can use prepend in the same way we would use include or extend by first defining the module and then using the keyword and name of the module within the class's definition, as below.

module IndoorPet
    def can_be_housebroken?
        true
    end
end  


class Animal 
end 

class Dog < Animal 
    prepend IndoorPet
end 

class Cat < Animal
    prepend IndoorPet
end 

class Rabbit < Animal 
    prepend IndoorPet
end
Enter fullscreen mode Exit fullscreen mode

In many ways, prepend operates like include. Both provide a class access to a module's methods as instance methods. Therefore, we can call the imported methods that we have prepended from our module on specific instances of a class, and not on the class itself.

roger = Rabbit.new("Roger")

roger.can_be_housebroken?
=> true 

Rabbit.can_be_housebroken?
NoMethodError: undefined method `can_be_housebroken?' for Rabbit:Class
Enter fullscreen mode Exit fullscreen mode

However, the difference between include and prepend is related to the location in the ancestry chain where the module is placed. With prepend, the module is not inserted between the class and it's superclass as it was with include. It is actually inserted at the very bottom of the ancestry chain, as seen below.

Rabbit.ancestors
=> [IndoorPet, Rabbit, Animal, Object, PP::ObjectMixin, Kernel, BasicObject]
Enter fullscreen mode Exit fullscreen mode

The importance of this distinction will be explored further in our next post!

include vs extend vs prepend

include

  • Provides a class with access to a module's methods as instance methods
  • Allows the methods to be called on individual instances of the class
  • Does not allow methods to be called on the class as a whole
  • Inserts module into ancestry chain between the class and superclass

extend

  • Provides a class with access to a module's methods as class methods
  • Does not allow the methods to be called on individual instances of the class
  • Allows methods to be called on the class as a whole

prepend

  • Provides a class with access to a module's methods as instance methods
  • Allows the methods to be called on individual instances of the class
  • Does not allow methods to be called on the class as a whole
  • Inserts module at the bottom of the ancestry chain

Top comments (4)

Collapse
 
vgoff profile image
Victor Goff

Early in this article it is stated:

Specifically, we used the include keyword to give our classes access to instance methods.

But those are not keywords, but methods:

>> defined? extend
=> "method"
>> defined? include
=> "method"
>> defined? prepend
=> nil
.. module Something
..   p defined?(prepend)
>> end
"method"
=> "method"
Enter fullscreen mode Exit fullscreen mode

I want to send some new programmers here to read up on this, but also want them to have the right names for things.

Collapse
 
mszul profile image
Mirosław Szul

Good articles, but I see one small bug (for learners): roger object can't be initialized with argument in this case, because Rabbit class doesn't allow it (Rabbit or Animal classes dont't have initalize method). Good luck :-)

Collapse
 
sahilbansal17 profile image
Sahil

Easy to follow and crisp explanation! Thanks!

Collapse
 
ravistm profile image
Ravishankar.T.M • Edited

thanks for the simple explanation!