It’s all under control.

Private & Protected in Ruby

Tech - RubyCademy
RubyCademy
4 min readMar 26, 2018

--

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, 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) that 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 have a better understanding 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 sends theprivate_message's message to the receiver self. So as we’re in a Receiver context and the message receiver is implicit then 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 enumerated above.

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

Kernel#send: the anarchist

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

Indeed, 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

Since Ruby 2.7

Ruby 2.7+ allows private methods to be called with self as receiver

puts "hello world!"            # => hello world!
self.puts "hello world!" # => hello world!
Object.new.puts "hello world!" # NoMethodError

Ruby Mastery

We’re currently finalizing our first online course: Ruby Mastery.

Join the list for an exclusive release alert! 🔔

🔗 Ruby Mastery by RubyCademy

Also, you can follow us on x.com as we’re very active on this platform. Indeed, we post elaborate code examples every day.

💚

--

--