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?
CodePudding user response:
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 aninitialize
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 stillRoadBike
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. DoesRoadBike
have its own implementation ofdefault_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.