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 < Bar
end

Foo.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 Bar
end

class Foo
  include Bar
end

Foo.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'
  end
end

module Bar
  def hello
    p 'hello'
    super
  end
end

class Foo < Baz
  include Bar
end

Foo.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 B
end

Foo.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'
  end
end

class Foo
  extend Bar
end

Foo.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 Baz
end

Foo.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.

 

Topics: Ruby on Rails, Coding & Development

Logan Serman

Written by Logan Serman

Enjoy the article? Share it!