Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ruby metaprogramming, defining multiple "inherited" functions

I want the following module to be included in a class I have:

module InheritanceEnumerator  
  def self.included(klass)  
      klass.instance_eval do  
        instance_variable_set('@subclasses',[])  
        def self.subclasses  
          @subclasses  
        end  
        original_method = self.respond_to?(:inherited) ? self.public_method(:inherited) : nil  
        instance_variable_set('@original_inherited_method', original_method)  
        def self.inherited(subclass)  
          @original_inherited_method.call(subclass) if @original_inherited_method  
          @subclasses<<subclass  
        end  
      end  
   end  
end  

What I'm trying to achieve is that I want my parent class to have references to direct children. I also need any other previous "inherited" methods set on my class by other stuff to stay in place. What am I doing wrong?

like image 518
Olivier Tremblay Avatar asked Nov 05 '22 09:11

Olivier Tremblay


1 Answers

Your code works in this situation (for me):

class C; include InheritanceEnumerator; end
C.subclasses #=> []
class C1 < C; end
class C2 < C; end
C.subclasses #=> [C1, C2]

But fails in the following situation:

class C11 < C1; end
C1.subclasses => NoMethodError: undefined method `<<' for nil:NilClass

This is because you are only initializing @subclasses when the module is included; but you are forgetting that subclasses of C also have access to the modules methods but do not explictly include it.

You fix this by doing the following:

def self.subclasses
    @subclasses ||= []
    @subclasses
end

def self.inherited(subclass)
    @original_inherited_method.call(subclass) if @original_inherited_method

    @subclasses ||= []
    @subclasses << subclass  
end

EDIT:

Okay, in future, please state what your problem is more fully and provide the test code you are using; as this was an exercise in frustration.

The following works fine with your code:

class C
    def self.inherited(s)
        puts "inherited by #{s}!"
    end

    include InheritanceEnumerator
end

class D < C; end #=> "inherited by D!"
C.subclasses #=> [D]

Perhaps the reason it wasn't working for you is that you included InheritanceEnumerator before you had defined the inherited method?

like image 121
horseyguy Avatar answered Nov 09 '22 15:11

horseyguy