How to Override a Variable in a Ruby Subclass Without Affecting The Superclass

How do I override a variable in a Ruby subclass without affecting the superclass?

Class constants does what you want, you just need to use them differently:

class Foo
TEST = "a"

def speak
puts self.class::TEST
end
end

class Bar < Foo
TEST = "b"
end

Bar.new.speak # => a
Foo.new.speak # => b

Can Ruby subclass instance variables _overwrite_ the superclass's (same name)?

The book (emphasis and addition mine):

If a subclass uses an instance variable with the same name as a[n instance] variable used by one of its ancestors, it will overwrite the value of its ancestor’s variable.

I know you don't have two instances of the same class; we're specifically discussing inheritance.

When a subclass uses an instance variable with the same name as an instance variable used by the superclass, there's a single instance variable. If the subclass changes the value of that instance variable, and the superclass accesses it, it gets the value set by the subclass.

When a subclass is instantiated, it acts "as-if" it's also an instance of the superclass. The way Ruby is implemented means that if the superclass has an instance variable @foo, the subclass can access it. This makes a distinction between the subclass's @foo and the superclass's @foo meaningless.

This is how subclasses may alter superclass behavior: by setting a value the superclass might use. If a subclass sets @foo = 42, and a superclass method accesses @foo, it sees 42. This may or may not be intended, hence the warning. It can lead to spectacularly frustrating debugging sessions.

class MyStack
def initialize
@my_array = []
end

def push(item)
@my_array << item
end
end

# Stack class that keeps a list
# of every item ever pushed.
class TrackingStack < MyStack
def initialize
super
@my_array = []
end

def push(item)
super
@my_array << item
end

def all_items_ever_pushed
@my_array
end
end

TrackingStack introduces a bug, because it inadvertently used the same name as the superclass's array used to hold the stack contents. If you weren't familiar with the superclass's implementation, this would cause confusion and bugs until you dug deeply enough to understand where the unintended behavior came from.

An instance of the superclass is just that: an instance of the superclass, and it's meaningless to talk about how an instance of the subclass will affect it, because they're completely unrelated.

Here's a rephrasing:

Subclassing can be risky when you don't control, or are unfamiliar with, the superclass implementation. One reason is because the introduction of an instance variable in the subclass may overwrite the value of a superclass instance variable, leading to unintended behavior.

Override ruby constant in subclass so inherited methods use new constant instead of the old?

I've done this by simply redefining the constant in the subclass, and then referring to it in methods as self.class::CONST in instance methods and self::CONST in class methods. In your example:

class SuperClass
CONST = "Hello, world!"
def self.say_hello
self::CONST
end
end

class SubClass < SuperClass
CONST = "Hello, Bob!"
end

SubClass.say_hello #=> "Hello, Bob!"

Initializing class instance variables of subclasses from the superclass

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.

Can you override a method by including a module?

I don't quite understand your question. What, exactly, do you think is "prevented" here, and by whom?

This is precisely how it is supposed to work. Module#include mixes in the module as the direct superclass of whatever class it is being mixed into. M is a superclass of Foo, so Foo#bar overrides M#bar, because that's how inheritance works: subclasses override superclasses, not the other way around. Nothing is being "prevented" here, of course you can still override Foo#bar in a subclass of Foo.

You can clearly see the ancestry:

class FooS; end
module M; end
class Foo < FooS; include M end

Foo.ancestors # => [Foo, M, FooS, Object, Kernel, BasicObject]

Ruby - initialize inheritance, super with only certain arguments?

Yes, you are committing a Cardinal Sin (obviously, you are aware of it, since you are asking about it). :)

You are breaking Liskov substitution principle (and probably some other named or unnamed rules).

You should probably extract another class as a common superclass, which does not contain occupation. That will make everything much clearer and cleaner.

Ruby - how to handle problem of subclass accidentally overriding superclass's private fields?

Instance variables have nothing to do with inheritance, they are created on first usage, not by some defining mechanism, therefore there is no special access control for them in language and they can not be shadowed.

Not only do I need to understand your
published interface, but I also need
to understand your private fields.

Actually this is an "official" position. Excerpt from "The Ruby Programming Language" book (where Matz is one of the authors):

... this is another reason why it is only safe to extend Ruby
classes when you are familiar with
(and in control of) the implementation
of the superclass.

If you don't know it inside and out you're on your own. Sad but true.



Related Topics



Leave a reply



Submit