Calling super class' super class method? - ruby

How can I get child to ignore what its parent thinks is fun and go straight to the grandparent's idea of fun?
Child still inherits from the parent, but it just doesn't agree with a couple methods.
Calling the method of the super class of the super class?
Also, is it considered poor design if I'm in a situation where the child doesn't agree with its parents but agrees with the parent's parents?
class Grandparent
def fun
#do stuff
end
end
class Parent < Grandparent
def fun
super
#parent does some stuff
end
def new_business
#unrelated to my parent
end
end
class Child < Parent
def fun
super
#child also does some stuff
end
def inherit_new_business
new_business
#stuff
end
end

It's generally easier in Ruby to get this kind of behavior through composition rather than inheritance. To accomplish that Modules are included that contain the specific behaviors you wish a class to have.
But if you absolutely have to use inheritance you can do this:
class Child < Parent
def fun
GrandParent.instance_method(:fun).bind(self).call
# fun child stuff
end
end
This will do exactly what it says. Grab the instance method fun from the GrandParent class, attach it to the current instance object self and call it.

Related

Can I call a subclass' private method from its parent class?

After reading about Ruby's access controls, I understand that a private method may only be called, implicitly, from within a class and within that class' subclasses. I have an example, though, where a class seems to be calling a private method default_chain on its subclasses, and it still works. Check out the following code (adapted from Sandi Metz' Practical Object-Oriented Design in Ruby):
class Bicycle
attr_reader :chain
def initialize(args={})
#chain = args[:chain] || default_chain
end
def parts
{
chain: chain
}
end
end
class RoadBike < Bicycle
def parts
super.merge(
handlebar_tape_color: "red"
)
end
private
def default_chain
"21-speed"
end
end
class MountainBike < Bicycle
def parts
super.merge(
suspension: "Manitou Mezzer Pro"
)
end
private
def default_chain
"10-speed"
end
end
RoadBike.new.parts # {:chain=>"21-speed", :handlebar_tape_color=>"red"}
MountainBike.new.parts # {:chain=>"10-speed", :suspension=>"Manitou Mezzer Pro"}
What's going on?
You're getting it wrong - in your example, there is no such a thing as the parent class calling children methods.
Methods/constants name lookup in Ruby always works "bottom up": first we check if the method is defined in object's class, then in object's class's superclass and so on (this is a huge simplification because Ruby's object model is more complicated, more on this later). So, in your example things happen in roughly the following order:
When you call RoadBike.new runtime checks if there is an initialize methods defined for the class RoadBike. There is no, so we use the implementation defined for its parent class - Bycicle (but the execution context stays the same - it is still RoadBike instance)
When executing Bycicle#initialize runtime encounters another method call - default_chain. At this moment we start method name resolving in the very same manner - starting from the RoadBike context. Does RoadBike have its own implementation of default_chain? Yes, it does, so we simply call it.
The following baby example makes it crystal clear, hopefully:
class Parent
def initialize
puts "Parent Initializer is called"
a
b
end
def a
puts "Parent a is called"
end
def b
puts "Parent b is called"
end
end
class Child < Parent
def b
puts "Child b is called"
end
end
pry(main)> Child.new
Parent Initializer is called
Parent a is called
Child b is called
In reality the methods/constants resolution machinery is more complicated(includes so-called singleton classes). This is a bigger topic that will not fit nicely in SO answer, so I strongly recommend reading "Metaprogramming Ruby 2" by Paolo Perotta where this model is wery well explained in great details from the very practical point of view.

Calling super, super class method with parameters in Ruby

I found the below working solutions (link 1, link 2) which call the grandparent method but without any parameters. Does anyone know how to call the grandparent method with parameters?
class GrandParent
def fun(word)
puts word
end
end
class Parent < GrandParent
def fun(word)
puts word + 'end'
end
end
class Child < Parent
def fun(word)
GrandParent.instance_method(:fun).bind(self).call
end
end
You can pass parameters directly to call like this:
class Child < Parent
def fun(word)
GrandParent.instance_method(:fun).bind(self).call(param1, param2)
end
end
call accepts parameters
GrandParent.instance_method(:fun).bind(self).call word
I don't know your use case, but this is a Really Bad Idea™. It creates unnecessary dependencies directly between Child and GrandParent such that all kinds of normally reasonable refactoring would result in a crash e.g. moving fun to only be implemented in Parent, changing Parent to subclass a different but similar parent, etc.

Accessing a child's constant from the parent's class

If a child inherits from the parent, it inherits the parents methods as well.
So how come an inherited method can't access the child's constants?
Example -
class Parent
def my_method
puts "Value of FOO is #{FOO}"
end
end
class Child < Parent
FOO = "bar"
end
Child.new.my_method #=> NameError: uninitialized constant Parent::FOO
Doesn't the inherited method run "inside" the child class?
How can I get around this?
No it doesn't. When you call Child.new.my_method it looks for the FOO constant in Parent and upwards.
I'm not going to question your approach and just provide you with a way to do what you want:
class Parent
def self.inherited(other)
other.define_singleton_method(:my_method) do
puts "Value of FOO is #{other.const_get(:FOO)}"
end
end
end
Now whenever you inherit from Parent the inherited hook will define a method my_method on the subclass. Notice that the scope of the block in the inherited method is still the class level scope of Parent. That's why I'm directly referencing the subclass (other) to get the constant.
A way around this would be to use other.instance_exec { ... }.
Update
#maxpleaner pointed out (with the explanation provided by #CarySwoveland) that you could also retrieve the class via self and then retrieve the constant from it.
I.e., just do
class Parent
def my_method
puts "Values of FOO is #{self.class.const_get(:FOO)}"
end
end
That works because when calling child = Child.new; child.my_method the value of self within my_method will be child. And the class of child is obviously Child which knows the constant FOO.

Get child constant in parent method - Ruby

In Ruby, is it possible to go about getting a child's constant when I've called a parent's method through the child?
Example:
class Tester
class Parent
def go
EWOCK
end
end
class Child < Parent
EWOCK = "EWOCKS rule"
end
end
Then call Tester::Child.new.go and desire "EWOCKS rule" to come back?
[Edit 3/31]
Wow I'm REALLY sorry guys. I completely screwed up the explanation.
Parent should have been Child and Child should have inherited from base.
The call should have been to Child and not Parent
Again, many apologies and thanks to those who replied attempting to understand my horrid writeup.
It is fixed now.
EDIT: this answer is correct, although Wayne's is the more ruby-ish way to approach the problem.
Yes it is.
Your implementation will not work, because the parent tries to resolve EWOK locally. Parent doesn't have EWOK defined. However, you can tell Ruby to look specifically at the class of the actual instance the method was called on, to get EWOK.
this will work:
class Parent
def go
self.class::EWOK
end
end
class Child < Parent
EWOK = "Ewoks Rule"
end
class Child2 < Parent
EWOK = "Ewoks are ok, I guess"
end
bob = Child.new
bob.go # => "Ewoks Rule"
joe = Child2.new
joe.go # => "Ewoks are ok, I guess"
what's going on here:
in Parent's 'go', "self" will refer to the instance of the object that 'go' is actually being called on. i.e., bob (a Child), or joe (a Child2). self.class gets the actual class of that instance - Child in the case of bob, or Child2 in the case of joe.
then, self.class::EWOK will retrieve EWOK from the correct class.
For a parent class to have access to a constant defined in a child class, wrap that constant in a method. Then the normal inheritance rules apply:
class Parent
def ewok
"Ewoks are lame"
end
end
class Child < Parent
def ewok
"Ewoks rule"
end
end
p Parent.new.ewok # Ewoks are lame
p Child.new.ewok # Ewoks rule
If the constant is expensive to initialize (a large hash, for example), the define it in a constant, but access it via a method:
class Parent
EWOK = {
# Enormous hash...
}
def ewok
EWOK
end
end

Calling super's super method

Is is possible to do something like super.super in the overriden method? That is, to bypass the direct parent's super and call "grandparent's" super?
This is not recommended, but what you want is possible like this:
grandparent = self.class.superclass.superclass
meth = grandparent.instance_method(:the_method)
meth.bind(self).call
This works by first getting the grandparent class, then calling instance_method on it to get an UnboundMethod representing the grandparent's version of the_method. It then uses UnboundMethod#bind and Method#call to call the grandparent's method on the current object.
you could modify the method's arguments to allow some kind of optional 'pass to parent' parameter. in the super class of your child, check for this parameter and if so, call super from that method and return, otherwise allow execution to continue.
class Grandparent; def method_name(opts={}); puts "Grandparent called."; end; end
class Parent < Grandparent
def method_name(opts={})
return super if opts[:grandparent]
# do stuff otherwise...
puts "Parent called."
end
end
class Child < Parent
def method_name(opts={})
super(:grandparent=>true)
end
end
ruby-1.9.2-p0 > Child.new.method_name
Grandparent called.
=> nil
otherwise i agree with #Femaref, just b/c something is possible doesn't mean it's a good idea. reconsider your design if you think this is necessary.
Considering this is breaking one of the principles of OOP (encapsulation), I dearly hope it isn't possible. Even the case of you trying to do this speaks of a problem with your design.

Resources