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):
I know you don't have two instances of the same class; we're specifically discussing inheritance.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.
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
Sorry, that is not what you are doing here:I want all subclasses to have a variable on their singleton class / eigenclass.
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.
Actually this is an "official" position. Excerpt from "The Ruby Programming Language" book (where Matz is one of the authors):Not only do I need to understand your
published interface, but I also need
to understand your private fields.
If you don't know it inside and out you're on your own. Sad but true.... 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.
Related Topics
Fixing The "Ruby Installation Is Missing Psych" Error
How to Get Meta Keywords Using Nokogiri
What's The Best Way to Test Delayed_Job Chains with Rspec
Ruby/Rails 3.1: Given a Url String, Remove Path
Rails - Understanding Application.Js and Application.CSS
Remove Adjacent Identical Elements in a Ruby Array
Rails3 Scope for Count of Children in Has_Many Relationship
Rails/Postgres, 'Foreign Keys' Stored in Array to Create 1-Many Association
Paperclip and Amazon S3 How to Do Paths
Importing CSV Data into a Ruby Array/Variable
Can You Specify The Http Method to Use with Sinatra's Redirect
How to Wait for System Command to End
"Previous Post" and "Next Post" Link in Show View (Nested Resources)