Home > database >  Why does this code works with protected, not private?
Why does this code works with protected, not private?

Time:12-06

I am confused about private vs protected here. I have read that it is impossible to call private and protected methods on the objects created outside of the class. And that I can use them in public methods. So, why does this code work with protected, not private?

class Student
  def initialize(name, grade)
    @name = name
    @grade = grade
  end

  def better_grade_than?(other_student)
    grade > other_student.grade ? true : false
  end

  protected
  def grade
    @grade
  end

end

class Joe < Student
end

class Bob < Student
end

joe = Joe.new('Joe', 88)
bob = Bob.new('Bob', 60)

puts joe.better_grade_than?(bob) # true
puts bob.better_grade_than?(joe) # false

If private, it outputs NoMethodError.

CodePudding user response:

In Ruby, protected methods are accessible to the class that defines it and and classes that inherit from it. On the other hand, private methods are only accessible by the class that defines it.

So in your example protected works because Joe and Bob both inherit from Student, so they can use it's protected methods.

CodePudding user response:

I have read that it is impossible to call private and protected methods on the objects created outside of the class.

This is wrong.

private

private means "can only be invoked by a message send with an implicit receiver of self or with an explicit receiver that is the literal pseudo-variable keyword self".

In other words, a private method qux can only be invoked by message send like this:

qux      # implicit receiver

or like this:

self.qux # `self` keyword as the explicit receiver

It can not be called with any other receiver. Not even with self as the receiver, unless it is the actual, literal pseudo-variable keyword self:

this = self
self.qux
# private method `qux' called (NoMethodError)
#
#     this.qux
#         ^^^^

You can try it yourself:

class C
  def foo = qux
  def bar = self.qux

  def baz
    this = self
    this.qux
  end

  private def qux; end
end

o = C.new

o.foo
o.bar
o.baz

I am not sure where you read the statement you quoted, but I recommend always reading an official source.

This is what section 13.3.5.3 Private methods of the ISO/IEC 30170:2012 Information technology — Programming languages — Ruby specification has to say, for example:

13.3.5.3 Private methods

A private method is a method whose visibility attribute is set to the private visibility.

A private method cannot be invoked with an explicit receiver, i.e., a private method cannot be invoked if a primary-expression or a chained-method-invocation occurs at the position which corresponds to the method receiver in the method invocation, except for a method invocation of any of the following forms where the primary-expression is a self-expression.

  • single-method-assignment
  • abbreviated-method-assignment
  • single-indexing-assignment
  • abbreviated-indexing-assignment

Note that the ISO Ruby Programming Language Specification has not been updated since 2012, so this is a slightly outdated definition. The specification was slightly simplified a couple of years ago. Nowadays, the second paragraph should probably read more like this:

A private method cannot be invoked with an explicit receiver, i.e., a private method cannot be invoked if a primary-expression or a chained-method-invocation occurs at the position which corresponds to the method receiver in the method invocation, except for a method invocation where the primary-expression is a self-expression.

But note that this doesn't make a difference here, since in your code you are not using a self-expression as the receiver anyway, so whether a self-expression is allowed in a limited number of cases or always is irrelevant.

The ruby/spec for private and for message sends also does not seem to be fully up-to-date, but again, the differences do not matter for your question.

The RDoc for Modules, specifically, the subsection on Visibility, however, is fully up-to-date and pretty clear:

The third visibility is private. A private method may only be called from inside the owner class without a receiver, or with a literal self as a receiver. If a private method is called with a receiver other than a literal self, a NoMethodError will be raised.

protected

protected means "can only be invoked by a message sent from a sender which is an instance of the module the method is defined in".

In other words, if you have a protected method m defined in module M like this:

module M
  protected def m; end
end

Then m can only be invoked by a message send where the sender is an instance of M:

kind_of?(M) #=> true
M === self  #=> true

receiver.m

I think a succinct way to phrase the test would be something like:

kind_of?(receiver.method(:m).owner) # must be `true`

Here's what section 13.3.5.4 Protected methods of the ISO/IEC 30170:2012 Information technology — Programming languages — Ruby specification has to say:

13.3.5.4 Protected methods

A protected method is a method whose visibility attribute is set to the protected visibility.

A protected method can be invoked if and only if the following condition holds:

  • Let M be an instance of the class Module in which the binding of the method exists.
    M is included in the current self, or M is the class of the current self or one of its superclasses.

If M is a singleton class, whether the method can be invoked or not may be determined in an implementation-defined way.

And the RDoc:

The second visibility is protected. When calling a protected method the sender must inherit the Class or Module which defines the method. Otherwise a NoMethodError will be raised.

Protected visibility is most frequently used to define == and other comparison methods where the author does not wish to expose an object’s state to any caller and would like to restrict it only to inherited classes.

So, why does this code work with protected, not private?

It doesn't work with private because here you are sending a message with an explicit receiver that is not the literal pseudo-variable keyword self:

grade > other_student.grade ? true : false
#       ↑↑↑↑↑↑↑↑↑↑↑↑↑

The receiver is other_student, but if grade is private, then it can only be invoked by a message send with an implicit receiver (grade) or with an explicit receiver of self (self.grade).

It does work with protected because other_student is an instance of Student.

  •  Tags:  
  • ruby
  • Related