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 literalself
, aNoMethodError
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 theClass
orModule
which defines the method. Otherwise aNoMethodError
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
.