Matthew Lindfield Seager

Matthew Lindfield Seager

Modules as Classes in Ruby

Today I learned that Ruby modules are actually implemented as classes under the hood.

When you include a module (which is kind of a class in disguise), it actually gets dynamically inserted into the inheritance tree.


Consider the example of a school. We might have a Person class to model every person in the school with some common attributes:

class Person
  attr_accessor :given_name
  attr_accessor :surname
end

Then we might have subclasses for students, staff and parents to model attributes specific to each group. Focusing on the student, our program might look like this:

class Person
  attr_accessor :given_name
  attr_accessor :surname
end

class Student < Person
  attr_accessor :year_level
end

To add attributes to staff and students that don’t apply to parents we could define a module and mix it in. Let’s say we want to be able to add students (e.g. Harry and Ron) and staff (e.g. Minerva McGonagall) to a house (e.g. Gryffindor). Our program now looks more like this:

class Person
  attr_accessor :given_name
  attr_accessor :surname
end

module House
  attr_accessor :name
  attr_accessor :colour
end

class Student < Person
  include House
  attr_accessor :year_level
end

Previously Student inherited directly from Person but now the House module (or, more accurately, a copy of it) gets inserted as the new parent class of Student. Under the hood the inheritance hierarchy now looks more like Student < House < Person.


That’s all well and good but Ruby doesn’t support multiple inheritance; each class can only have one parent. So how does including multiple modules work? It turns out, they each get dynamically inserted into the inheritance tree, one after another.

Stretching our example a little further, if we had some attributes common to parents and students, we might use another module. Perhaps we want to record the household so we can model that Ron, Ginny and Mr & Mrs Weasley are all part of the “Weasley” household (I’m stretching this example quite far, hopefully it doesn’t snap and take out an eye). Our student class might now look like this:

class Person
  attr_accessor :given_name
  attr_accessor :surname
end

module House
  attr_accessor :house_name
  attr_accessor :house_colour
end

module Household
  attr_accessor :household_name
  attr_accessor :address
end

class Student < Person
  include House
  include Household
  attr_accessor :year_level
end

Under the hood, Ruby first inserts House as the new parent (superclass) of Student but then on the very next line it inserts Household as the parent of Student. The inheritance chain is now Student < Household < House < Person.