Search code examples
rubyirbprivate-methodspublic-method

Why is my top-level method public (as opposed to private) on all classes when I declare it in IRB?


I'm currently reading "The Well-Grounded Rubyist", and on page 196 I see the following:

Suppose you define a method at the top level:

def talk
  puts "Hello"
end

....

A method that you define at the top level is stored as a private instance method of the Object class. The previous code is equivalent to this:

class Object

  private

  def talk
    puts "Hello"
  end
end

...

To illustrate, let's extend the talk example. Here it is again, with some code that exercises it:

puts "Trying 'talk' with no receiver..."
talk
puts "Trying 'talk' with an explicit receiver..."
obj = Object.new
obj.talk

The first call to talk succeeds; the second fails with a fatal error, because it tries to call a private method with an explicit receiver.

I wanted to reproduce this on my local, so I put the above code in a Ruby file I created. I did indeed get the results mentioned in the book:

$ ruby talk.rb 
Trying 'talk' with no receiver...
Hello
Trying 'talk' with an explicit receiver...
Traceback (most recent call last):
talk.rb:22:in `<main>': private method `talk' called for #<Object:0x00007f9a8499c3e0> (NoMethodError)

I also tried the following, which produced the same error as running the code via the Ruby interpreter:

irb(main):008:0> load 'talk.rb'
Trying 'talk' with no receiver...
Hello
Trying 'talk' with an explicit receiver...
Traceback (most recent call last):
        4: from /Users/richiethomas/.rbenv/versions/2.5.3/bin/irb:11:in `<main>'
        3: from (irb):8
        2: from (irb):8:in `load'
        1: from talk.rb:22:in `<top (required)>'
NoMethodError (private method `talk' called for #<Object:0x00007ffb219c95e0>)

Next, I tried the same code in irb, and this time I got the following strange results:

irb(main):001:0> def talk
irb(main):002:1> puts "Hello"
irb(main):003:1> end
=> :talk
irb(main):004:0> puts "Trying 'talk' with no receiver..."
Trying 'talk' with no receiver...
=> nil
irb(main):005:0> talk
Hello
=> nil
irb(main):006:0> puts "Trying 'talk' with an explicit receiver..."
Trying 'talk' with an explicit receiver...
=> nil
irb(main):007:0> Object.new.talk
Hello
=> nil

As you can see, in the last code example, I was able to call Object.new.talk and have it print Hello as if .talk were a public method on the Object instance.

My question is- why is the talk method public on the Object class when I implement it directly in the REPL, but private when I implement it in a file and load it into the REPL (and also when I run that same file directly in my CLI via the Ruby interpreter)?


Solution

  • Both irb and pry (sidenote: I strongly encourage to use the latter) tweak their input to declare all methods as public (during E stage of REP loop):

    ▶ def foo; end
    #⇒ :foo
    ▶ public_methods.grep /foo/
    #⇒ [:foo]
    

    That’s it, no magic.


    That is done mostly to simplify playing with it in scenarios when one defines the method here and then wants it to be accessible from e.g. there. In REPL it is worth it to make everything accessible everywhere.

    def pretty_print; self.inspect; end
    class A; ...; end
    class B; ...; end
    
    A.new.pretty_print
    B.new.pretty_print
    

    One should not pay too much attention to encapsulation, SRP, etc while playing the sandbox.


    In general, it’s like declaring the module with generic helpers and including it everywhere for free.