1

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

Logain
  • 4,259
  • 1
  • 23
  • 32
  • Your lines with `puts` are not helpful at. All. What result do you get from them? – sawa Oct 03 '14 at 04:33
  • The issue is entirely not clear. At the beginning, you wrote that you do not want to share the variable. Then later, you wrote as if you want a variable to be shared. Among what do you want a variable to be shared, and among what do you want it not to be shared? – sawa Oct 03 '14 at 04:37
  • You are right @sawa, I'll edit it with the outputs. Anyway, that's why I made the rubyfiddle – Logain Oct 03 '14 at 04:37
  • 1
    Don't try to let people to read something in another webpage. We don't care what you write in rubyfiddle; we won't look at it. State your question within this page. – sawa Oct 03 '14 at 04:38
  • ...in part because there is no guarantee that rubyfiddle will be around for future readers. – Cary Swoveland Oct 03 '14 at 17:48

1 Answers1

2

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.

7stud
  • 46,922
  • 14
  • 101
  • 127
  • Great! It seems there is no need for the instance_eval – Logain Oct 03 '14 at 04:59
  • I probably didn't express myself correctly, sorry. It was a remanent of my original question where I stated that I didn't want a class variable. What I meant was "I want all subclasses to have the variable and the accessor behaviour". If I understand correctly, I add that behavior by modifying the eigenclass. I still don't understand the need of the instance_eval though, since it seems to work just fine without it. – Logain Oct 03 '14 at 05:14
  • @AlvarezAriel, Although it's undocumented, apparently singleton_class accepts a block, which behind the scenes does the instance_eval(), so you do not need to explicitly write that...however any experienced programmer is going to wonder about that, so I think it's clearer to be explicit about the instance_eval()--especially when that behavior is undocumented. – 7stud Oct 03 '14 at 05:32
  • 1
    @Alvarez Ariel, **I still don't understand the need of the instance_eval though** Because attr_accessor() is a private method, which means that you can't explicitly specify a receiver when you call that method. Instead, ruby uses self to call that method. Therefore you have to execute that method in a region of your code where self is equal to the singleton class. instance_eval() allows you to create a block where self is equal to the object that called instance_eval(). – 7stud Oct 03 '14 at 05:37
  • Thanks! But what I meant was that after removing all the `subclass.singleton_class.instance_eval do; attr_accessor :variable end`. It still behaves like I want it to. I added some extra information on the question trying to understand why that is, but I'm not sure if it's correct. Al least I hope it will help you understand my motivation. – Logain Oct 03 '14 at 06:04
  • @AlvarezAriel, After consulting my lookup diagrams here: http://stackoverflow.com/questions/23848667/ruby-method-lookup-path-for-an-object, the very last diagram fits your situation, so the instance_eval() block is unnecessary because a subclass's singleton class will inherit the attribute accessor from the superclass's singleton class. – 7stud Oct 03 '14 at 16:33
  • 1
    @Alvarez Ariel, **Now, what I'm really not understanding is where x is.** Instance variables attach themselves to whatever object is the receiver when the writer is used, e.g. `subclass.variable = 'Hi'`; ...or whatever object is self when a statement such as the following executes: `@x = 'hello'`. **If I do B.instance_variables it shows []** Instance variables don't exist until they are created, instance variables are a property of an instance--not a class, and each instance of a class can have different instance variables. – 7stud Oct 03 '14 at 16:40
  • A bit of clarification: That does not mean that B does not have instance variables due to the fact that it is a class. B is in instance of Class, and therefore B can have instance variables. – 7stud Oct 04 '14 at 00:14