4

The two invocations of an instance method carry different semantics. The first call to bar works as expected.

class T
  def foo
    bar      # <= This works. "bar" is printed.
    self.bar # <= EXCEPTION: `foo': private method `bar' called for ...
  end

  private

  def bar
    puts 'bar'
  end
end

t = T.new
t.foo

I'd like to understand why. What causes self.bar to have a different semantics, throwing an exception? I'm not interested in the exception per se since I can work around with the dubious removal of the private label, but primarily interested in the semantic discussion.

sawa
  • 165,429
  • 45
  • 277
  • 381
Sniggerfardimungus
  • 11,583
  • 10
  • 52
  • 97
  • I thought I knew this one. Then I questioned myself, then I questioned Ruby. IIRC it's something to do with self referencing `T` and not the actual instance – Cjmarkham Apr 05 '18 at 18:56

1 Answers1

4

Private methods can not be called with explicit receiver like self. This means that you can call a private method from within a class it is declared in as well as all subclasses of this class.

Here is a good article about that. It explains well why code like yours will raise NoMethodError.

On wikibooks there is also a good explanation of visibility levels in ruby.

For the reference you can bypass this with Object#send but generally it is considered as a bad practice to do so.

Martin
  • 4,042
  • 2
  • 19
  • 29
  • Good answer. One might ask why Ruby doesn't see that `self.bar` is being called from within the class and permit it, but all Ruby knows at the time the statement is being executed is that `bar` is being called on an instance of `T`, so she has no choice but to raise the exception. – Cary Swoveland Apr 05 '18 at 21:17
  • 1
    @CarySwoveland what about a setter like `private def bar=(value); end` which has to be invoked with an explicit receiver? In that case Ruby permits `self.bar=123` but raises an exception for `t.bar=123`. She seems to be quite aware of the receiver. – Stefan Apr 05 '18 at 23:13
  • 1
    Good point, @Stefan. Presumably that's a special case, else `bar` could not be invoked (without using a form of `send`). I'm wondering how Ruby implements that. Does she check to see if the method is a setter and if so , look more closely? In a similar vein, I created a private method `class` and found that if I wrote `def m; self.class; end` I got the exception about calling a private method with an explicit receiver. If I wrote `def m; class; end` I got a syntax error, presumably because Ruby regarded `class` as a keyword. – Cary Swoveland Apr 06 '18 at 00:35
  • @CarySwoveland maybe even at the parser level? Either way, there is no _technical_ reason why Ruby doesn't allow private method calls with an explicit receiver of `self`. It can be distinguished just fine. – Stefan Apr 06 '18 at 06:34