A colleague shared the following code today:

# ruby code

module ModuleA
  def test
    "something #{super}"
  end
end

class ClassA
  prepend ModuleA

  def test
    self.class.name
  end
end

class ClassB < ClassA
  def test
    self.class.name
  end
end

ClassA.new.test #=> "something ClassA"
ClassB.new.test #=> "ClassA"

And then a question followed: if we’ve pre-pended ModuleA to ClassA and if ClassB inherits from ClassA, why doesn’t B.new.test include the “something” string? :/

After being needlessly confused for a few minutes we figured out what was happening. And it’s quite funny that it confused us. To keep things simple, think of it the following way:

  1. Pre-pending a module to a class sort-of adds the module’s methods to the end of the class. So in ClassA, ModuleA’s test method is added in the end. Therefore A.new.test has the output that it does.

  2. In ClassB, we’ve created our own test method. This will of course ignore ClassA’s test method. And this is why the “something” string is absent from the output.

There. That explains the behavior while keeping the language simple enough for beginners to understand.

Now, let’s try pre-pending ModuleA to ClassB and see if it “fixes” things:

# ruby code

ClassB < ClassA
  prepend ModuleA

  def test
    self.class.name
  end
end

ClassB.new.test #=> "ClassB"

But why? :’(

Check out the ancestors of both classes:

# ruby code

ClassA.ancestors
#=> [ModuleA, ClassA, Object, Kernel, BasicObject]

That tells us that pre-pending modules makes the module a descendant of the class. Well, sort-of.

Let’s check out the ancestors of ClassB:

# ruby code

ClassB.ancestors
#=> [ClassB, ModuleA, ClassA, Object, Kernel, BasicObject]

Since we’ve pre-pended the module to ClassB’s parent class, the module appears to be an ancestor of ClassB. Pre-pending it to ClassB and making it a descendant (while it’s already an ancestor) just doesn’t make sense. :/

And then we had some coffee.