4
votes

Why does the instance method "protected_class_method" not exist for the "Module" class in Ruby, while the "public_class_method" and "private_class_method" instance methods do exist for the "Module" class?

This does not follow the pattern of the "private", "protected", and "public" instance methods all being defined for the "Module" class.

3
The difference between "private" and "public" depends on who's asking, but I'm not sure Ruby's reflection features properly categorize things all the time. "Protected" is just a way of saying "public for some, private for others". - tadman
Matz says, "If you understand the current behavior of protected visibility, you will understand why protected_class_method is not useful at all. That's why we don't have it." - Cary Swoveland

3 Answers

3
votes

For me, protected methods only make sense as instance methods.

Protected methods can be called by other instances of the same class.

class Student
  def initialize(age)
    @age = age
  end
  def older_than?(other)
    age > other.age
  end
  protected
  def age
    @age
  end
end

You can't directly call age on a Student instance

student1 = Student.new(21)

student1.age
NoMethodError: protected method `age' called for #<Student:0x514d058 @age=21>

But student1 can reference student2's age

student2 = Student.new(23)

student2.older_than?(student1)
 => true

So you can see how an instance's protected methods uniqueness is its ability to be referenced from another instance.

I can't see how you would use a class "protected method"... there's no scenario similar to the above.

EDIT

Thanks to Cary Swoveland completely messing with my mind, I realise you can do the following...

class Class
  def show_k(klass)
    klass.k
  end
  protected
  def k
    "This is k"
  end
end

Now if I do

String.k 
NoMethodError: protected method `k' called for String:Class

But if I do...

Integer.show_k(String)
=> "This is k"

Possible because classes are instances of the Class class.

I'm still not sure how I'd use this, but there you go.

1
votes

Let's define a class with one class method.

class K
  def self.k
    'hi'
  end
end

def self.k is just a shorthand way to define an instance method k on K's singleton class. Let's do that and make it protected.

class K
  class << self
    protected def k
      puts 'hi'
    end
  end
end

We find that

K.k
  #=> NoMethodError: protected method `k' called for K:Class

The only way to invoke this method is (as with private methods) is with an implicit receiver:

class K
  k
end
  #=> 'hi'

By definition, a protected instance method defined on a singleton class could be invoked by an instance of the same class, but one can cannot create instances of singleton classes.

This behaviour is the same as private class methods, so it would serve no purpose to have protected ones as well.

1
votes

There is a difference between protected and private when declaring class methods (instance methods on a class's singleton class) if you consider inheritance and usage of explicit receivers in class methods. The following sample was tested using Ruby 2.5.

class A
  class << self
    protected def pro
      puts 'pro'
    end
  end
end

class B < A
  class << self
    # Works
    def explicit_receiver_test
      self.pro
    end

    # Works
    def implicit_receiver_test
      pro
    end
  end
end

begin
  B.pro # Throws: NoMethodError
rescue NoMethodError => e
  puts e.message # Prints: protected method `pro' called for B:Class
end
B.explicit_receiver_test # Prints: pro
B.implicit_receiver_test # Prints: pro

class C
  class << self
    private def pri
      puts 'pri'
    end
  end
end

class D < C
  class << self
    # Fails
    def explicit_receiver_test
      self.pri
    end

    # Works
    def implicit_receiver_test
      pri
    end
  end
end

begin
  D.pri # Throws: NoMethodError
rescue NoMethodError => e
  puts e.message # Prints: private method `pri' called for D:Class
end
begin
  D.explicit_receiver_test # Throws: NoMethodError
rescue NoMethodError => e
  puts e.message # Prints: private method `pri' called for D:Class
end
D.implicit_receiver_test # Prints: pri