ruby-on-railsrubyooptrailblazer

Identical inheritance pattern in multiple classes


I have the following situation:

class A < CommonParent
  ... some code ...

  class IdenticalDescendent < self
    identical_statement_0
    identical_statement_1
  end
end

class B < CommonParent
  ... some other code ...

  class IdenticalDescendent < self
    identical_statement_0
    identical_statement_1
  end
end

I have this situation a lot. Like, there are about forty IdenticalDescendent classes in my app. I like the pattern, it allows me to call A::IdenticalDescendent or B::IdenticalDescendent or whatever to access certain related behaviours in different domains (specified by A or B). For reasons, I can't just completely abstract the problem away by re-designing the behaviour clustering.

So the general form of my question is how do I automate the generation of IdenticalDescendent in all of these. There ARE descendants of CommonParent that don't invoke this pattern, so the action probably shouldn't happen there. I imagine it should happen in a mixin or something, but I find that if I just try to do:

class A < CommonParent
  include CommonBehaviour

  ... some code ...
end

module CommonBehaviour
  ... what ...
end

I can't figure out how to write CommonBehaviour to allow for the IdenticalDescendent to descend from the including class.

Help me StackOverflow, you're my only hope.


Solution

  • I believe you can automate your pattern by using the callback (hook) Class#inherited:

    class CommonParent
      def self.inherited(klass)
        return unless klass.superclass == CommonParent
        klass.const_set(:Descendent, Class.new(klass) do
          def x
            puts "in x"
          end
        end)
      end
    end
    
    class A < CommonParent
      def a
        puts "in a"
      end   
    end
    
    d = A::Descendent.new #=> #<A::Descendent:0x007f99620172e8> 
    d.a                   #   in a
    d.x                   #   in x
    
    class B < CommonParent
      def b
        puts "in b"
      end
    end
    
    d = B::Descendent.new #=> #<B::Descendent:0x007f99618b18f0> 
    d.b                   #   in b
    d.x                   #   in x
    d.a                   #=> NoMethodError:... (as expected)
    

    Note that, without:

    return unless klass.superclass == CommonParent
    

    the creation of A::Descendent would trigger inherited with klass => Descendent, causing an anonymous subclass of Descendent to be created, etc., resulting in a "stack level too deep exception."