The power of Object private methods in Ruby
Vitalii Paprotskyi
Posted on February 22, 2022
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
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
>> greeting = Greeting.new
=> #<Greeting:0x00007f7a8f0f0da0>
>>
>> greeting.english
=> "Hi"
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"
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
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
>> greeting = Greeting.new
=> #<Greeting:0x00007f7a8f0b8a18>
>>
>> greeting.call_english_without_receiver
=> "Hi"
>>
>> greeting.call_english_with_receiver
=> "Hi"
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
There are three very important things to remember about an implicit receiver:
- Whenever a message is sent without specifying a receiver, it will be sent to an implicit receiver.
- The implicit receiver is always the
self
object. -
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>
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.
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?
>> 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
But, wait a second! Why is puts
private?
If puts
is private, why can we call it?
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
>> 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"
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
>> 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)
>> # 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)
>> # 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
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...
Experiment and learn!
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
>> 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
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.
Posted on February 22, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.