Ruby 2.7 allows calling a private method with self.


Before Ruby 2.7

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.

class Counter
  def reset
    self.count = 0 # allowed
    puts self.count # raises error
  end

  private
  attr_accessor :count
end
Counter.new.reset

Traceback (most recent call last):
        3: from /Users/puneetsutar/.rbenv/versions/2.5.3/bin/irb:11:in `<main>'
        2: from (irb):10
        1: from (irb):4:in `reset'
NoMethodError (private method `count' called for #<Counter:0x00007fe56509df10 @count=0>)

As shown above, calling writer method self.count = 0 does not raise an error.

But, calling reader method self.count raises an error. This is inconsistent behavior.

A more practical example would be something as below.

class Counter
  def initialize
    self.count = 0
  end

  def increment
    self.count += 1
  end

  def decrement
    self.count -= 1
  end

  private
  attr_accessor :count
end
counter = Counter.new
counter.increment
Traceback (most recent call last):
        3: from /Users/puneetsutar/.rbenv/versions/2.5.3/bin/irb:11:in `<main>'
        2: from (irb):18
        1: from (irb):7:in `increment'
NoMethodError (private method `count' called for #<Counter:0x00007fe3f4103670 @count=0>)

Initializing the Counter object works fine, but when we try to increment the counter, we get an error. To understand why we get the error, let’s expand the code within Counter#increment; self.count += 1 is equivalent to self.count = self.count + 1. self.count is not legal.

After Ruby 2.7

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

If we execute the above example in Ruby 2.7, then we won’t get any error.

This feature was requested and discussed on RubyLang at Feature #16123 and Feature #11297