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
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
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]
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
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'
In addition to include/extend, Ruby 2.0+ adds the
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.