DEV Community

Vitalii Paprotskyi
Vitalii Paprotskyi

Posted on

The power of Object private methods in Ruby

We all know this simple and powerful method puts. Without diving into much details, it just outputs the object you provide it to the console. Almost every Ruby "Hello World" tutorial has this snippet of code.

Note: I'm using using Ruby 3.0.1

>> puts "Hello World"
Hello World
Enter fullscreen mode Exit fullscreen mode

In short, every Ruby developer is familiar with this method. But, have you ever wonder why you can call this method everywhere without any receiver? (And not only puts, but also p, print, rand, sleep and a lot more)

What is a receiver?

When you call a method in Ruby, you send a message to an object. The object that receives a message is called (drum roll...) a receiver! If an object has a method that corresponds to the message name, it's going to call that method. Here's a simple example.

class Greeting
  def english
    "Hi"
  end
end
Enter fullscreen mode Exit fullscreen mode
>> greeting = Greeting.new
=> #<Greeting:0x00007f7a8f0f0da0>
>>
>> greeting.english
=> "Hi"
Enter fullscreen mode Exit fullscreen mode

Here, greeting is the receiver. It receives the message english. It is able to respond to that message because it has the method that corresponds to it.

The most important thing is: when you send a message(call a method), you always send it to some receiver(object). There's always a receiver. You can't send a message to no receiver.

By the way, we can use send method to call english method. In such a way calling a method looks more like sending a message πŸ˜‰

>> greeting.send(:english)
=> "Hi"
Enter fullscreen mode Exit fullscreen mode

Now that we know what the receiver is, let's get back to our beloved puts. Calling puts without any receiver looks more like calling a function, not a method. Maybe it's a function? But, Ruby is object oriented language, there are no functions... Nonetheless, I'm able to call puts everywhere without specifying a receiver. Why am I able to do that?

>> puts "puts can be easily called from here"
puts can be easily called from here
>>
?> class A
?>   puts "and here"
>> end
and here
>>
?> module B
?>   puts "and of course from here"
>> end
and of course from here
>>
?> class C
?>   def c
?>     puts "long time no see"
?>   end
>> end
>> C.new.c
long time no see
Enter fullscreen mode Exit fullscreen mode

To understand this, let's first understand what is an implicit receiver.

What is an implicit receiver?

Take a look at this example.

class Greeting
  def english
    "Hi"
  end

  def call_english_without_receiver
    english # The implicit receiver is self.
  end

  # This method works the same as the above one.
  def call_english_with_receiver
    self.english
  end

  def get_implicit_receiver
    self
  end
end
Enter fullscreen mode Exit fullscreen mode
>> greeting = Greeting.new
=> #<Greeting:0x00007f7a8f0b8a18>
>>
>> greeting.call_english_without_receiver
=> "Hi"
>>
>> greeting.call_english_with_receiver
=> "Hi"
Enter fullscreen mode Exit fullscreen mode

We send call_english_without_receiver message to the greeting object. call_english_without_receiver method sends english message without any receiver. If no receiver is specified, self becomes the receiver. Because of that, call_english_with_receiver works exactly the same as call_english_without_receiver. Simple enough. But, what is self? In our case it's the greeting object.

>> greeting.get_implicit_receiver == greeting
=> true
Enter fullscreen mode Exit fullscreen mode

There are three very important things to remember about an implicit receiver:

  1. Whenever a message is sent without specifying a receiver, it will be sent to an implicit receiver.
  2. The implicit receiver is always the self object.
  3. self is present at any point in Ruby program. It changes when context does(basically, self is going to be different in a class, outside of a class, in a method... and so on).
>> # Here self is the main object. It is always present
>> # outside of any methods, classes and modules.
>> p self
main
>>
?> class A
?>   # Here self is the class object.
?>   p self
>> end
A
>>
?> module B
?>   # Here self is the module object.
?>   p self
>> end
B
>>
?> class C
?>   def c
?>     # Here self is the class instance.
?>     p self
?>   end
>> end
>> C.new.c
#<C:0x00007f904e0afa10>
>>
>> C.new.c
#<C:0x00007f904e066748>
Enter fullscreen mode Exit fullscreen mode

Image description

Okay, now we know that whenever puts message is sent(method is called) with no receiver, it is sent to the self object(called on the self object). But, self could be absolutely any object in Ruby. Does it mean that every object has puts method? The answer is YES. Every single object in Ruby responds to puts message.

Why can all objects respond to puts message?

The first thing that I should point out is that all classes in Ruby inherit from Object class, which means that Object is a superclass of any class in Ruby. Let me show you an example. We can find out whether an object's class inherits from a certain class by using kind_of? method.

>> 1.class
=> Integer
>> 1.kind_of? Object # Integer inherits from Object.
=> true
>>
>> [1].class
=> Array
>> [1].kind_of? Object # Array inherits from Object.
=> true
>>
?> class A
=> end
>> A.class
=> Class
>> A.kind_of? Object # Class inherits from Object 🀯
=> true
>>
?> module B
=> end
>> B.class
=> Module
>> B.kind_of? Object # Module inherits from Object 🀯
=> true
>>
>> self
=> main
>> self.class # main object is actually an instance of Object.
=> Object
>> self.kind_of? Object
=> true
>>
>> # You got the point :)
>> # Don't worry about examples with A class and B module 
>> # if you don't understand them.
>> # Basically, everything in ruby is an object.
>> # Which means that classes and modules are objects as well.
>> # And since every object has its class, we can 
>> # call #class method on any object.
Enter fullscreen mode Exit fullscreen mode

You may ask, what does it have to do with puts method?
The thing is that Object class includes Kernel module, and Kernel defines puts method. It means that puts method becomes available for all instances of Object class and all instances of Object subclasses. Which means, puts becomes available for all objects! Isn't it awesome?

Image description

>> Object.kind_of? Kernel # #kind_of? also checks whether a module is included.
=> true
>>
>> Kernel.private_instance_methods.include? :puts
=> true
>> # Which means that Object must have #puts as well.
>>
>> Object.private_instance_methods.include? :puts
=> true
>>
>> Integer.private_instance_methods.include? :puts
=> true
>>
?> class A
>> end
>> A.private_instance_methods.include? :puts
=> true
Enter fullscreen mode Exit fullscreen mode

But, wait a second! Why is puts private?

If puts is private, why can we call it?

Image description

What does it mean for a method to be private? It means that the method can only be called with an implicit receiver, or with self. Here's an example.

class MyClass
  def my_public_method
    "Public"
  end

  def call_private_without_self
    my_private_method
  end

  def call_private_with_self
    self.my_private_method
  end

  private

  def my_private_method
    "Private"
  end
end
Enter fullscreen mode Exit fullscreen mode
>> obj = MyClass.new
>> obj.my_public_method
=> "Public"
>>
>> obj.my_private_method
private method `my_private_method` called for #<MyClass:0x00007fd43d219418> (NoMethodError)
>>
>> obj.call_private_without_self
=> "Private"
>>
>> obj.call_private_with_self
=> "Private"
Enter fullscreen mode Exit fullscreen mode

See? I can't call my_private_method on obj object, because obj is the explicit receiver, and private methods does not allow that. I can only call my_private_method via call_private_without_self and call_private_with_self, because the first one uses the implicit receiver, and the second one uses self. And that's the main difference between public methods and private methods. Public methods can be called with an explicit receiver, an implicit receiver and with self. Private methods can only be called with an implicit receiver and with self.

Since puts is a private method just like MyClass#my_private_method method, the same rules apply to it.

class MyClass
  def call_puts_without_self
    puts "Without self"
  end

  def call_puts_with_self
    self.puts "With self"
  end
end
Enter fullscreen mode Exit fullscreen mode
>> instance = MyClass.new
>> instance.call_puts_without_self
Without self
>>
>> instance.call_puts_with_self
With self
>>
>> instance.puts "You won't be able to call puts like this"
private method `puts` called for #<MyClass:0x00007fba20a67ae8> (NoMethodError)
Enter fullscreen mode Exit fullscreen mode
>> # Here's one more example.
?> class A
?>   puts "Without self"
?>   self.puts "With self"
>> end
Without self
With self
>>
>> A.puts "Ain't gonna print this"
private method `puts` called for A:Class (NoMethodError)
Enter fullscreen mode Exit fullscreen mode
>> # And here's a funny example :)
>> puts "Without self"
Without self
>>
>> self.puts "With self"
With self
>>
>> # Let's assign self to a variable.
>> main_obj = self
>>
>> main_obj.puts "Explicit receiver, it won't work anyway"
private method `puts` called for main:Object (NoMethodError)
>>
>> # Even though this is true. 
>> main_obj == self
=> true
Enter fullscreen mode Exit fullscreen mode

And this is the reason why puts is always called without a receiver. Ruby language design allows us to not think about all those details and simply call puts like some magical function. And it's not only about puts, it's about all private methods you can find in Object class.

>> methods = Object.private_instance_methods
>> methods.include? :raise # yeah, it's a method, not a keyword
=> true
>>
>> methods.include? :rand
=> true
>>
>> methods.include? :print
=> true
>>
=> # And so on... 
Enter fullscreen mode Exit fullscreen mode

Experiment and learn!

Image description

Let's try to experiment with what we learned about private methods in Object class.

# Opening Object class to add a private method to it.
class Object
  private

  def debug_puts(str)
    puts "puts from #{self}: #{str}"
  end
end
Enter fullscreen mode Exit fullscreen mode
>> debug_puts "hello"
puts from main: hello
>>
?> class A
?>   debug_puts "hello"
>> end
puts from A: hello
>>
?> class B
?>   def b
?>     debug_puts "hello"
?>   end
>> end
>> B.new.b
puts from #<B:0x00007f88a2143368>: hello
Enter fullscreen mode Exit fullscreen mode

I do not recommend doing something like this in a real project πŸ˜…, but it's always good to experiment. It helps you learn more.

Conclusion

Isn't Ruby awesome? I've been programing in Ruby for quite a long time, and only recently I asked myself this question: "why do I call "puts" without any receiver?". The goal of this article is to not teach details of Ruby language design(I'm definitely not the one to do that πŸ˜†) but ask yourself questions if you don't understand something. You will learn a lot on the way of finding the answer. There's no magic in programing, everything has its explanation.

Top comments (1)

Collapse
 
juanvqz profile image
Juan Vasquez

yes, ruby it's awesome! LOL (such a great question), I've asked question but didn't investigate (a lot), I'll do it more. I appreciate it, good time here!