Calling a parent instance variable from a sub class in ruby
I have to call an instance variable initialized in a parent Class from a sub class. I know that I can solve using "@@profile" instead of "@profile" but it needs to be thread-safe.
class ParentClass
attr_reader :profile
def initialize(profile:)
@profile = profile
end
def call_sub
SubClass.new().run()
end
end
class SubClass < ParentClass
def initialize()
@retries = 0
end
def run()
puts "PROFILE: #{@profile.inspect}"
end
end
pc = ParentClass.new(profile: "Me")
pc.call_sub
p = ParentClass.new(profile: "Me")
puts "PROFILE: #{p.profile}"
ruby test.rb
PROFILE: nil <--- Why is nil ?
PROFILE: Me
You are overriding ParentClass#initialize
.
Ruby does not magically invoke overridden methods. That would kind of defeat the point of overriding in the first place: for example, often, the reason why you override a method in a subclass, is that the subclass implementation can make use of its knowledge of the subclass to improve performance. It wouldn't make sense for Ruby to invoke the un-optimized version in addition to the optimized one.
However, another big purpose of overriding is differential code reuse: reusing code by only implementing the difference in behavior. In order to do that, Ruby has the super
keyword, which allows you to retry the method lookup starting one level up in the ancestors chain.
Here's what that would look like in your case:
class SubClass < ParentClass
def initialize(*)
super
@retries = 0
end
end
This solves the problem with the un-initialized instance variable.
However, there are several other problems with your code. For example, here:
SubClass.new().run()
You are sending the message new
to SubClass
without any arguments. By default, Class#new
is implemented roughly like this:
class Class
def new(...)
obj = allocate
obj.initialize(...)
obj
end
end
[Technically, initialize
is private
, so we need to break encapsulation here by using something more like obj.__send__(:initialize, ...)
.]
In other words, new
passes its arguments on to initialize
. But you have defined ParentClass#initialize
with one mandatory keyword parameter profile:
, so theoretically, we need to pass an argument to super
… but what should we pass? So, instead, we have defined SubClass#initialize
to take arguments and pass them on to ParentClass#initialize
.
However, you are passing no arguments to SubClass::new
here, that we could pass on, and in fact, it isn't clear what arguments we could pass here.
I would like to help you here, but to be honest, the code simply does not make sense, so I don't see how to fix it. Superclasses must never know about their subclasses, in fact, they typically cannot know about their subclasses: many languages allow you to add subclasses after the fact, and Ruby is no exception. Therefore, a superclass knowing about its subclass is a sign that something in your design is very, very, very wrong.
Also note that the very idea of a "parent instance variable" does not make sense: "parent" refers to the class hierarchy, but instance variables belong to instances … that's why they are called "instance" variables, after all.