Today I learned that Ruby modules are actually implemented as classes under the hood.
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.