My Ruby newsletter is available here. Feel free to subscribe :-)

Private & Protected: a matter of Message

As a Ruby method is — behind the scene — a message handler associated with a block of instructions that returns an object, the private and protected policies are strongly correlated with the Ruby message concept.

So, in order to understand private and protected policies, let’s have a quick recall on the Ruby message concept.

Message, Receiver and Message Handler

A message is composed of a name (commonly a symbol) and an optional payload (the arguments list).

A message requires a receiver (an object) which responds to this message via a message handler (a method).

The message sender is always the calling object context.This can be the main object if the message is called from outside of a class context.

We can explicitly send a message to a receiver by using the Kernel#send method

  receiver        name   payload
| | |
__________ ______ ______
"a-string".send(:split, '-', 3)

In the above example, the message is composed of:

  • a receiver: "a-string"
  • a message name: :split
  • a payload: '-', 3
  • and a sender which is the main object

So "a-string" responds to the message named :split via the message handler String#split.

However, you should be more familiar with the dot syntax

  receiver   name payload
| | |
__________ _____ ______
"a-string".split('-', 3)

So here, the message split is implicitly sent to the receiver "a-string" with the payload ('-', 3) .

Now that we’ve a better overview of what’s a message in Ruby, let’s detail the notion of private and protected methods.

Private methods

In Ruby, a private method (or private message handler) can only respond to a message with an implicit receiver (self). It also cannot respond to a message called from outside of the private message handler context (the object)

class Receiver
def public_message
private_message
end
  def self_public_message
self.private_message
end
  private
def private_message
puts "This is a private message"
end
end
irb> Receiver.new.public_message
This is a private message
=> nil
irb> Receiver.new.self_public_message
NoMethodError: private method `private_message' called for #<Receiver:0x007b>
irb> Receiver.new.private_message
NoMethodError: private method `private_message' called for #<Receiver:0x007b>

In Receiver#public_message, the Receiver instance implicitly send theprivate_message's message to the receiver self. So as we’re in a Receiver context and the message receiver is implicit then the Receiver#private_message can respond to the message.

Receiver#self_public_message explicitly calls the private method for the receiver self . As a private message handler cannot respond to a message with a receiver, then a NoMethodError is raised.

An explicit call to Receiver.new.private_message will raise a NoMethodError because the message is sent from outside of the private_message context (which should be an instance of Receiver).

Protected methods

In Ruby, a protected method (or protected message handler) can only respond to a message with an implicit/explicit receiver (object) of the same family. It also cannot respond to a message sent from outside of the protected message handler context.

class Receiver
def public_message
protected_message
end
  def self_public_message
self.protected_message
end
  protected
def protected_message
puts "This is a protected message"
end
end
class Mailbox < Receiver
def mb_public_message
::Mailbox.new.protected_message
end
end
irb> Receiver.new.public_message
This is a protected message
=> nil
irb> Receiver.new.self_public_message
This is a protected message
=> nil
irb> Mailbox.new.mb_public_message
This is a protected message
=> nil
irb> Receiver.new.protected_message
NoMethodError: protected method `protected_message' called for #<Receiver:0x007fbed691bdf0>

In Receiver#public_message, protected and private methods share the same policy.

Receiver#self_public_message explicitly call the protected method for the receiver self . The protected message handler Receiver#protected_message can respond to the message because it contains:

  • a receiver of the same family
  • and the message is sent by the Receiver object.

Mailbox.new.mb_public_method also works fine for the same reasons as enumerated above.

Receiver.new.protected_message raises a NoMethodError because the message is sent from outside of the Receiver object.

Kernel#send: the anarchist

The Kernel#send method has a specificity that can be useful in some cases (testing, etc..).

In effect, when a message is sent by using this method, the private and protected policies are bypassed

class Receiver
def public_message
protected_message
end
  def self_public_message
self.protected_message
end
  protected
def protected_message
puts "This is a protected message"
end
  private
def private_message
puts "this is a private message"
end
end
irb> Receiver.new.send(:private_message)
this is a private message
=> nil
irb> Receiver.new.send(:protected_message)
This is a protected message
=> nil

Voilà !


Thank you for taking the time to read this post :-)

Feel free to 👏 and share this Medium post if it has been useful for you.

Here is a link to my last medium post: RVM Gemsets.

Like what you read? Give Mehdi Farsi a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.