rubyeigenclass

Initializing class instance variables of subclasses from the superclass


Given the superclass in the code below, I want all subclasses to have some instance variable.

The code below does that, but fails to properly initialize that variable for all possible subclasses.

I opened the eigenclass of my superclass. Here is the code (also in rubyfiddle):

class SomeSuperClass
  class << self
    attr_accessor :variable
    @variable = ': )' # This does't seem to have any effect
  end
  self.variable = 'This only works for the superclass'
end

class SubClass < SomeSuperClass; end

puts SomeSuperClass.variable # => 'This only works for the superclass'
puts SubClass.variable # => ''

SomeSuperClass.variable = 'I am the superclass'
SubClass.variable = 'I am the subclass'

puts SomeSuperClass.variable # => 'I am the superclass'
puts SubClass.variable # => 'I am the subclass'

I would like to have all possible sublcasses initialized. On the first two puts, only SomeSuperClass.variable is initialized. I don't know how to initialize this variable for all possible subclasses. Any ideas?

The best solution I found is to lazy-initialize the variable, overriding the accessor, as in:

class SomeSuperClass
  def self.variable
    @variable ||= 'Hi'
  end
end

The motivation:

I need all subclasses of a given class, lets call it Vigilant, be able to monitor some things happening on their direct subclasses. This information is stored on the class, and therefore have a different state for each one.

I can't use a class variable, since two classes A < B would be modifying the same variable. I can't access directly to the subclasses either, so I needed a way to give all subclasses of Vigilant the capacity for storing and retrieving the information about their subclasses.

By defining the accessors opening the eigen class, lets say:

A.singleton_class.instance_eval { attr_accessor :x }

All subclasses Bclass B < A; end are now able to do B.x, because a method (an accessor) was added to its superclass eigen class, and therefore can be found on the lookup.

And the first example shows that B.x is different from A.x

Now, what I'm really not understanding is where x is; the variable, not the accessors. If I do B.instance_variables it shows [], same with B.singleton_class.instance_variables


Solution

  • I want all subclasses to have a variable on their singleton class / eigenclass.

    Sorry, that is not what you are doing here:

    puts SomeSuperClass.variable # => 'This only works for the superclass'
    puts SubClass.variable # => '
    

    Why would you think that writing

    SomeSuperClass.variable 
    

    is equivalent to the pseudo code:

    SomeSuperClassSingletonClass.variable
    

    or the real code:

    SomeSuperClass.singleton_class.variable
    

    A class and it's singleton class are two different Classes.

    In addition, this code:

      class << self
        attr_accessor :variable
        @variable = ': )' # This does't seem to have any effect   
      end
    

    does not create an accessor for that @variable, the same way that this code:

    class Dog
      attr_accessor :x
    
      @x = 'hello'
    end
    
    puts Dog.x
    

    ...does not create an accessor for that @x variable:

    --output:--
    undefined method `x' for Dog:Class (NoMethodError) 
    

    What attr_accessor() does is this:

    class Dog
      def x
        @x
      end
    
      def x=(val)
        @x = val
      end
    
      #=====
    
      @x = 'hello'
    end
    

    Those methods have nothing to do with the class instance variable @x, which was defined outside all the defs. @variables are looked up (or set) on whatever object is self at that instant. The only objects that can call those defs are instances of class Dog, and therefore x will be looked up (or set) on a Dog instance--not the Dog class.

    Also note that when the line @x = 'hello' executes, self is equal to the Dog class, therefore @x attaches itself to the Dog class.

    I don't think you have a use case for setting an instance variable on a singleton class. Here is what it seems like you are trying to do:

    class SomeSuperClass
      class << self
        attr_accessor :variable
      end
    
      self.variable = 'hello'
    
      def self.inherited(subclass)
        subclass.singleton_class.instance_eval do
          attr_accessor :variable
        end
    
        subclass.variable = "Hi"
      end
    
    end
    
    class SubClass < SomeSuperClass
    end
    
    puts SomeSuperClass.variable
    puts SubClass.variable
    
    
    --output:--
    hello
    Hi
    

    That code creates what are known as class instance variables. If you think you have a use case for singleton class instance variables, let's hear it.