Back

In-Depth Ruby: Modules & Include vs. Extend

Ruby provides a construct called a Module which is sort of like a thin Class. At first, Ruby devs might look at Modules like a “junk drawer” of code that is just thrown around and used to organize files, but there is a method to this madness. The Ruby class Class inherits from Module and adds things like instantiation, properties, etc – all things you would normally think a class would have. Because Module is literally an ancestor of Class, this means Modules can be treated like classes in some ways.

Most importantly to understand is how the Ruby ancestor chain works. Like all OOP languages, Ruby supports inheritance. When you inherit from a class, the superclass is added to your ancestor chain. You can view the ancestors of any class by calling ancestors on it:

class Foo < BarendFoo.ancestors#=> [Foo, Bar, Object, ..., BasicObject]

As mentioned, you can find Module in the array Class.ancestors.

When you include a module into your class, the module is added to your class’s ancestor chain – just like a class. This makes include just a form of inheritance, there isn’t anything special about it.

module Barendclass Foo  include BarendFoo.ancestors#=> [Foo, Bar, Object, ..., BasicObject]

All of the methods in Bar are added to Foo as instance methods. Because Bar is in the chain, you can even call super from it to call methods above it in the chain, whether they are defined on Modules or Classes.

class Baz  def hello    p 'world'  endendmodule Bar  def hello    p 'hello'    super  endendclass Foo < Baz  include BarendFoo.new.hello#=> hello world

Even though Bar is a module, super still calls up the chain and so Baz#hello is also called. It is worth noting that Bar is added to the ancestor chain in front of Baz. When a module is included, it is always added directly on top of it’s including class. This can get confusing when adding multiple modules, since they are included in “reverse” order:

class Foo  include A  include BendFoo.ancestors#=> [Foo, B, A]

When A is included, it is inserted directly above Foo. But when B is included, it is also inserted directly above Foo, so it ends up landing before A.

Include vs Extend

include is easy enough to understand, it adds the module’s methods as instance methods to it’s including class. You can think of extend doing the same thing, but instead adding the methods as class methods.

module Bar  def hello    p 'hello'  endendclass Foo  extend BarendFoo.hello # no .new!# => 'hello'

Prepend

In addition to include/extend, Ruby 2.0+ adds the prepend method. prepend is similar to include, but instead inserts the module before the including class in the inheritance chain.

class Foo  include Bar  prepend BazendFoo.ancestors#=> [Baz, Foo, Bar]

In Ruby you can re-open any class and redefine the methods on it – it doesn’t seem useful to override methods on a class using a prepended module as opposed to redefining them. Where prepend comes in handy is when you still need the original implementation of the method, because prepended modules can override methods but still use super to call the original implementation.

Logan Serman
Logan Serman