Published on

The mysterious life of public, private, and protected in Ruby

Ruby Software engineering

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

I don't send any gifts

But you can subscribe to receive content related to Ruby

    Unsubscribe at any time.