Ruby object interfaces: Public x Private

Ruby object interfaces: Public x Private

Ruby beginners tend to skip the public/private classes until later in their learning course and people coming from other languages may find the way Ruby interfaces work quite unorthodox. In this article I will bring some light into this subject.

What are public and private methods?

If you are coming from other languages the notion of public and private methods may not be new to you, but for the sake of people learning Ruby as their first language let me explain what they are and why they exist.

Let’s take the following class as an example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Notebook
  def initialize
    @annotations = []
    @max_pages   = 5
  end

  def add_note(note)
    write(note) if notebook_has_blank_space?
  end

  def write(note)
    @annotations << note
  end

  def notebook_has_blank_space?
    @annotations.size < @max_pages
  end
end

If we only use the add_note method outside this class, in our application, we should be fine, as the knowledge of how to add a note (should) belong to the Notebook class only.

But if we start using all the methods from the class everywhere, we are sort of throwing the Object Orientation paradigm out through the window. It would make the class just a random namespace where we store some code. That’s not very nice.

By default all methods declared in Ruby are public, but if we add this little piece of joy called private, things can be different:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Notebook
  def initialize
    @annotations = []
    @max_pages   = 5
  end

  def add_note(note)
    write(note) if notebook_has_blank_space?
  end

  private

  def write(note)
    @annotations << note
  end

  def notebook_has_blank_space?
    @annotations.size < @max_pages
  end
end

From now on the if you try to access the write or notebook_has_blank_space? methods from the outside of the Notebook class Ruby would respond like that:

1
2
3
my_notebook = Notebook.new
my_notebook.write
# => NoMethodError: private method `write' called for #<Notebook:(...)>

But when called from the inside of the class everything will work fine:

1
2
Notebook.new.add_note("I have to buy socks")
#=> ["I have to buy socks"]

One thing to remember aside from buying socks: even when inside of the class, if you try calling self.write it will fail. You must call only write for private to work*.

*: This is the difference between Private and Protected, but for the sake of simplicity I will not enter in this subject.

What is the purpose of Ruby interfaces?

In the end of this article I will teach you a way to call your private methods from the outside of it’s class. So, if “breaking the rules” is possible, what is the purpose of defining methods as private?

Well, as we will see, making a method private makes it a little harder to call it where you shouldn’t, so it serves as a guide to future developers (which includes the future you) for interacting only with the stable interface from each class.

Stable versus Unstable

But what do I mean by stable? Well, the public methods from your classes are the ones you are promising to keep changes at a minimum (hence, stable), and the private are the ones you made no such promise (therefore unstable).

If your entire application is expecting your add_note method (public) from Notebook class to take one argument and return an array of notes, things would break in a lot of ways if you changed that method behavior.

The unstable interface (private) on the other hand could be modified, improved and refactored as desired, as long as in the end the public interface still works as it should, and nothing would break.

Reasons to avoid making all methods public

Testing gets easier

Let me tell you a secret: you don’t have to test private methods.

Don’t start commemorating yet, you should take the time you saved from testing private methods to make sure your public interface is very well tested. At the end it is the public methods that matter for the application as a whole.

Gives you confidence in future refactors

When you know that a set of methods are not being called anywhere but inside their classes, you can get a lot more confident in refactoring them, getting them more perfomant, more readable, etc.

Warns you of classes with too many responsibilities

When you realize you are being forced to let too many methods of a class as public, it may be an indicator of the need of splitting the class in more classes.

One useful tip of writing good code is to keep each class with as little responsibilities as possible (ideally just one). And it’s not me saying that, Robert C. Martin (a.k.a Uncle Bob) said it in his book Principles of Object Oriented Design.

Breaking the rules (the send method)

Ok, I promised to tell you how to break the rules, but it doesn’t mean you should.

Remember that this is a resource to be used with care, otherwise you risk ruining the whole purpose of making a private interface.

You can use the method send on any class to access any method of it, including private methods. It works like that:

1
2
3
  my_notebook = Notebook.new
  my_notebook.send(:write, "Note: This is a parameter")
  #=> ["Note: This is a parameter"]

You pass the method name as a symbol as the first argument, then the desired method arguments as the next ones.


Found it useful? Want to add something? Share with us in the comments below!

Victor A.M.

Victor A.M.
Yet another passionate web developer, mostly experienced with Ruby on Rails and Vue.js.
Currently living in São Paulo and working at Plataformatec.

Vue.js Advanced: Mastering Events

In this post we will take a deep dive into the Vue.js event system, talking about the good practices, helpful tricks and common pitfalls that you will can face. Continue reading

Rails transactions: The Complete Guide

Published on April 17, 2016

Rails 5: Meet the Active Record OR Query

Published on April 10, 2016