The mysterious life of public, private, and protected in Ruby
It seems that there is nothing more that you can learn about access modifiers in Ruby. Public, private, and protected are well known among Ruby developers. I thought so too. However, I found a few things that are still valuable to learn and may be surprising to you.
Access modifiers are methods
Not ordinary, but still methods. Let’s take a look at a very standard example of how we define certain levels of visibility:
class Person
def name; end
private
def age; end
def location; end
end
It’s evident that age
and location
methods are private, but you can also pass arguments to the private
method to achieve the same:
class Person
def name; end
def age; end
def location; end
private :age, :location
end
The result is the same, but we use private
as an ordinary method. Sometimes you can see such a solution, so it is good to know how it works. However, the preferred way of defining access levels is to group methods from the public through private to protected. As you may also know, all methods are public by default.
Difference between private and protected
How often do you see protected methods in the source code or define them in your code? I bet not often. Based on my experience, many developers don’t know the difference between private
and protected
methods, so they won’t use protected
when they should.
When the method is protected, you can call it like a public one, but only if you call it in the context of a class from the same family. If we have an Employee
class and we define the protected method:
class Employee
protected
def my_protected_method; end
end
You won’t be able to call Employee.new.my_protected_method
directly, but you can call it inside of the family member:
class Director < Employee
def call(employee)
employee.my_protected_method
end
end
This code won’t raise an error, and the method will be executed correctly:
employee = Employee.new
director = Director.new
director.call(employee)
Proper coding style for access modifiers
I mentioned before that you should define access modifiers starting with public methods, then private and protected at the end. What are the other coding style rules for access modifiers?
Calling object’s methods with a symbol
As you may know, with a pinch of meta-programming, you can call an object’s method in a dynamic way where the method name is saved in a variable:
method_name = :call
my_object = MyObject.new
my_object.send(method_name)
Don’t do it. We shouldn’t call private or protected methods, and with send
you won’t even notice it. Use public_send
instead; it will work only on public methods and will raise a standard error when attempting to trigger a non-public method:
method_name = :call
my_object = MyObject.new
my_object.public_send(method_name)
If you really want to call send
and don’t care about the access modifiers, you shouldn’t be doing this anyway. Use __send__
instead, as some objects define the send
method, and you won’t achieve what you want:
method_name = :call
my_object = MyObject.new
my_object.__send__(method_name)
Testing private methods
Don’t do it. Methods are private for a reason. The logic for a private method can change a lot, while the interface of the public method shouldn’t change. However, suppose you really feel like you should test the private method because something important is happening there. In that case, it probably means that you have to refactor your code and extract it to a separate class that is easily testable.
Private class method
I often see class methods that were designed to be private, but they are not:
class Employee
private
def self.call; end
end
Well, this won’t work as I can still call Employee.call
without any error. If you want to make the class method private, you have two solutions. You can open a singleton class and then use private
there:
class Employee
class << self
private
def call; end
end
end
If you don’t want to open the singleton class explicitly, you can use the private_class_method
method:
class Employee
def self.call; end
private_class_method :call
end
The result will be the same.
Private constant
I noticed a similar situation with constants. The following code won’t work as you may expect:
class Employee
private
MY_CONSTANT = 1
end
Our constant is still public, and we can call Employe::MY_CONSTANT
without any problem. To make it private, we have to use the private_constant
method:
class Employee
MY_CONSTANT = 1
private_constant :MY_CONSTANT
end
Changes in Ruby 3
With the third version of Ruby, you can define the attributes access level in the same line as the attr_accessor
:
class Employee
private attr_accessor :name, :age
end