ruby access modifiers, different output with different versions 2.5 - 2.7

Viewed 88

I am trying to run this code and it gives different output with different versions of ruby 2.5 - 2.7

code:

class ParentClass
    def the_public_method
        self.method1
    end

    private

    def method1
        puts "The private has been called"
    end
end

class ChildClass < ParentClass 
    def test
        self.method1
    end
end

ParentClass.new.the_public_method 
ChildClass.new.test

on ruby 2.5 it gives:

Traceback (most recent call last):
    1: from main.rb:19:in `<main>'
main.rb:3:in `the_public_method': private method `method1' called for 
#<ParentClass:0x000056367ee0b388> (NoMethodError)
Did you mean?  method
               methods
exit status 1

on ruby 2.7 it gives:

The private has been called
The private has been called

I think the first output is correct with the older version of ruby.. any feedback?

3 Answers

I think the first output is correct with the older version of ruby.. any feedback?

Both outputs are correct. The specification was changed in Ruby 2.7, so naturally Ruby 2.7 behaves different.

Originally, the rule for private methods was "private methods can only be called without an explicit receiver".

However, this means that you can't use private setters, because foo = :bar is a local variable assignment and self.foo = :bar is not allowed.

So, the rule was changed to "private methods can only be called without an explicit receiver, except for setters, where the literal pseudo-variable self is allowed as the receiver".

But, this still doesn't account for things like self + 2 or self.foo += 2 where either +, foo, or foo= are private, and many, many other corner cases.

For a while, the Ruby developers tried to cope with this by either ignoring some of those corner cases, or adding an ever more complex set of exceptions, but really, the solution is rather simple: change to rule to "private methods can only be called with the literal pseudo-variable self as the explicit or implicit receiver".

And that is the rule that stands since Ruby 2.7.

Ruby 2.7 allows calling a private method with self

Before Ruby 2.7, calling a private write/assignment method with literal self as the receiver was allowed, but calling any other private method with self would throw a NoMethodError error.

Ruby 2.7 aims at standardizing the interaction between self and private methods. The above inconsistency has been fixed in Ruby 2.7.

So the first output is correct before Ruby 2.7, the second output is correct after Ruby 2.7.

In Ruby, a private method is still accessible from inherited classes, but used to require a non-explicit received (i.e. an implicit call, like mehtod1 but not obj.method1 or self.method1)

As stated by @eux, this last requirement has been relaxed in ruby 2.7 so you can now call self.method too.


Another quirk with the visibility in Ruby is that it's linked to the instances and not the class itself. This explain the behaviour of private, and let's you understand the following code:

class Foo
  def initialize(name)
    @name = name
  end

  def ==(rhs)
    name == rhs.name
  end

  private

  attr_reader :name
end

f = Foo.new("bar")

f == f # NoMethodError

Here the NoMethodError occues because attr_reader :name is private, so you cannot access the method name of another object. To enable this behaviour, use protected

Related