Public, Private and Protected methods in Ruby
- 08 November 2016
- Ruby
- Public, Private and Protected methods in Ruby
Yes, this topic has been discussed many times and almost all people know that methods in Ruby can be public, private and protected. But it's not enough to know which access levels we have. Main point here to understand a difference. For example difference between private and protected levels of an access is not that obvious. So today I would like to talk about that difference.
Let's start from the simplest one - public.
Public
By default all methods in Ruby are public. It means that if we create method hi
for a User
class, it will be accessible for all User
objects at any place in your code:
class User
def hi
"hi"
end
end
u = User.new
puts u.hi # => hi
This very simple example shows: if we don't mark that method as private or protected - it will be public by default.
Private
To make methods private we can use private
method. If we don't pass any params to it - all methods below private
will be private:
class User
def hi
"hi"
end
def hello
"hello"
end
private
def secret
"secret"
end
def internal
"internal"
end
end
u = User.new
u.hi # => "hi"
u.hello # => "hello"
u.secret # => error: private method `secret' called
u.internal # => error: private method `internal' called
As we see from example methods secret
and internal
which placed after private
are not accessible for object or User
class for outside world. If we try to call those methods we will get an error:
private method `secret' called for #User:0x007f9a8284bd30 (NoMethodError)
To get list of object's private methods we can use method private_methods
:
u = User.new
puts u.private_methods(false).inspect # => [:secret, :internal]
Argument false
shows that we want to see private methods only of User
class.
Another way to make method private - pass method's name to private
method explicitly. This approach is used rarely, but still it's better to know about such possibility.
Our previous example could look like this:
class User
def hi
"hi"
end
def hello
"hello"
end
def secret
"secret"
end
def internal
"internal"
end
private :secret, :internal
end
u = User.new
u.private_methods(false) # => [:secret, :internal]
Line private :secret, :internal
shows that we want to make secret
and internal
methods private. As I mentioned earlier Ruby developers use this approach not that often. The main usage is to write private
and place all private methods below.
So private methods can not be used from outside, but we still can use it inside a class:
class User
def hi
"hi, i'll not tell you the #{secret}"
end
private
def secret
"secret"
end
end
u = User.new
u.hi # => hi, i'll not tell you the secret
In hi
method we can call secret
method and display its content properly.
What's the main idea of splitting methods by private, protected and public? Why it's bad idea to make all methods public?
The answer lies in the proper object-oriented design. Each class should have simple and nice interface. By class interface I mean methods and their arguments. Public methods define interface of class. Private methods responsible for internal logic and there is no need to expose them for outside world. Keep an eye on interfaces of your classes. It's a good idea to make methods private if they used only inside methods of class.
Writing stable code means that you write classes with interfaces that change really rarely and people can rely on those interfaces.
We can change internal implementation as many times as we want because that will not affect interface of class and will not break other parts of application. But as soon as we change public method of class - we have to go through all code and find usages of that method and change it to support new interface.
Let's get back to private methods and consider one more interesting idea. At first sight it looks complicated, but: private methods can not be called with explicitly defined receiver. Receiver it's an object on which we call that method. Even inside a class if we pass self
as a receiver that will not work.
It's easy to understand this idea by simple example:
class User
def say_secret_with_self
self.secret
end
def say_secret
secret
end
private
def secret
"secret"
end
end
u = User.new
u.say_secret_with_self # => `say_secret_with_self': private method `secret' called for #User:0x007ffdcc037e48 (NoMethodError)
u.say_secret # => "secret"
Inside class we tried to call private method as self.select
- and we got an error, the same one that we got when tried to call secret
from outside:
private method secret' called for #User:0x007ffdcc037e48
.
But if we call just secret
inside a class - that will work. So we will be able to call private methods just without explicit receiver. In our case it's just a secret
, but not a self.secret
.
Protected
I hope that you're not so frustrated by last example and continue reading. That's good because we are at the most interesting part of this article :)
What we know about protected methods: they're not accessible for outside world and we can define them using protected
method:
class User
def secret
"secret"
end
protected :secret
end
u = User.new
u.secret # => protected method `secret' called for #User:0x007fc7409384d8 (NoMethodError)
Or more convenient way:
class User
protected
def secret
"secret"
end
end
u = User.new
u.secret # => protected method `secret' called for #User:0x007fc7409384d8 (NoMethodError)
The question is: what's the difference between protected
and private
?
There is one main difference: protected methods support explicit definition of receiver inside class where it's being called:
class User
def say_secret_with_self
self.secret
end
protected
def secret
"secret"
end
end
u = User.new
u.say_secret_with_self # => "secret"
If I change protected
to private
- self.secret
will throw an error. But everything works with protected
.
It means that inside User
class we can call secret
for each object of User
.
Let's consider following example:
class User
attr_reader :name
def initialize(name)
@name = name
end
def ==(other_user)
self.secret == other_user.secret
end
protected
def secret
"#{name}.#{name.length}"
end
end
bob = User.new("Bob")
john = User.new("John")
bob == john # => false
User
class accepts name
param on initialization. Also we defined method ==
which we will use for comparing two objects. This check will compare two secrets
for users - current user and user we receive as a param other_user
. Since protected
methods allow us to define receiver - we will use that: self.secret == other_user.secret
. Notice, that inside definition of User
class we have an access to secret
method for other_user
user as well.
I've changed secret
method a bit. It just preforms a secret string from name and length, so for Bob it would be "Bob.3" and for John it's "John.4".
Logic is not that important because more interesting part that we can call protected method secret
inside definition of class User
, also we can explicitly set receiver of that method.
You did it! You've read this long article to the end! Public, Protected and Private it's a really important topic. Once it settled in your mind it will not look complicated anymore.
Those who read this article to this part I want to show one hack in Ruby that allows us to call private and protected methods on objects.
We can do that not by calling method straight away, but using send
method:
class User
private
def secret
"sic! it's a secret"
end
end
u = User.new
u.send(:secret) # => sic! it's a secret
Enjoy! :)