Overwriting and Overriding with define_method

Recently we stumbled upon this inheritance issue, which seemed very weird at the first:

class A
  def talk
    'A'
  end
end

class B < A
  def self.define_talk
    define_method :talk do
      super() << 'B'
    end
  end
end

class C < B
  define_talk
 
  def talk
    super << 'C'
  end
end

> C.new.talk
 => "AC"

The talk addition from class B doesn’t appear, even though define_talk is triggered by Class C. C’s super call seems to ignore its direct parent B.

Overriding vs Overwriting

The reason for that is the delayed execution of define_talk, which adds the talk method to C rather than B. The previous definition of C is equivalent to this:

class C < B
  # calling define_talk is equivalent to
  def talk
    super << 'B'
  end
 
  def talk
    super << 'C'
  end
end

The second definition of talk doesn’t override the first one, it overwrites it: The first definition is completely gone and therefore no longer available as super.

Overriding with Anonymous Modules

To fix this we need to ensure that B defines the method on B or another untouched inheritance layer. This layer can be an anonymous module created and included by the define_talk method:

class B < A
  def self.define_talk
    mod = Module.new do
      define_method :talk do
        super() << 'B'
      end
    end
    include mod
  end
end

Now C’s definition of talk only overrides the one of the anonymous module, which is therefore still available as super.

> C.new.talk
 => "ABC"