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

Time:07-23

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:

  1. 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)

  2. 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.

  • Related