Method Visibility in Ruby
The context in which methods can be called is defined by the three levels of method visibility in Ruby: public
, private
, and protected
. These access levels control how encapsulation is preserved and are essential to Ruby object-oriented programming.
Public
, private
, and protected
are not actual keywords in Ruby; rather, they are instance methods of the Module class. These methods, when used in a class or module definition, can change the visibility of previously specified methods by taking method names (as strings or symbols) as arguments, or they can dynamically change the visibility of methods written underneath them.
With code samples, the three access levels are explained as follows:
Public Methods
An object’s public methods specify its anticipated behavior and make up its public API.
Access: There are no limitations on the use of public methods, and they can be invoked from anywhere.
Default: In Ruby classes, methods are by default public.
Exceptions: Methods written at the top level outside of a class are defined as private instance methods of Object
, and the initialize
method is always implicitly private. These are the two main exceptions.
Code Example (Public Method):
The public method “speak
” in the following example can be invoked directly on the Cat
instance:
class Cat
def initialize(name)
@name = name
end
# This method is public by default
def speak
puts "I'm #{@name} and I'm 2 years old"
end
end
new_cat = Cat.new("garfield") #=> <Cat:0x2321868 @name="garfield">
new_cat.speak #=> I'm garfield and I'm 2 years old
# Public method called from outside the object scope.
Output
I'm garfield and I'm 2 years old
Private Methods
For a class’s internal implementation details, private methods are utilized.
Access Restriction: An explicit receiver is never allowed when calling a private method.
Invocation Rule: They must be called in a functional manner, which implies that they are implicitly called on self
(for example, by just using method_name
). A private method can therefore only be invoked by instance methods that belong to the same class (or its subclasses).
Inheritance: Subclasses inherit private methods and have the ability to call and override them.
Code Example (Private Method):
The public hint
method can implicitly invoke the secret
method below, but an explicit effort to call it on the object instance is unsuccessful:
class SecretNumber
def initialize
@secret = rand(20)
end
# Public method that calls the private method implicitly
def hint
puts "The number is #{"not " if secret <= 10}greater than 10."
# Calling `secret` without a receiver (implicit call on self)
end
private
def secret
@secret
end
end
s = SecretNumber.new
s.hint # The number is greater than 10. (Works)
# s.secret
# NoMethodError: private method `secret' called for #<SecretNumber:...> (Fails)
Output
The number is not greater than 10.
Even from within the defining class, a NoMethodError
will occur if you try to call a private method using an explicit receiver, like self.method_private
.
Protected Methods
Instances of the same class (or subclass) must be able to access each other’s internal methods, which is where protected methods come in handy.
Access: Within the implementation of the class that defines the protected method or any of its subclasses, the protected method can be called.
Invocation Rule: Protected methods can be directly called on another object, unlike private methods, as long as the object is a member of the calling object’s class or a subclass.
Purpose: They are frequently used to construct accessors, like comparison logic implementations, that let instances of a class share internal state.
Code Example (Protected Method):
In this example, the Account
class uses a protected accessor (balance
) so that an Account
instance can directly compare its internal balance against another Account
instance:
class Account
def initialize(balance)
@balance = balance
end
attr_reader :balance # Creates the getter method `balance`
protected :balance # Makes the getter method protected
def greater_balance_than(other)
# Allows explicit call `other.balance` because `self` and `other`
# are both instances of (or subclasses of) Account.
return @balance > other.balance
end
end
# Example usage demonstrating why protected is needed:
a = Account.new(100)
b = Account.new(50)
puts a.greater_balance_than(b) # => true
# If `balance` were private, attempting to call `other.balance` would raise a NoMethodError.
# If `balance` were public, anyone could access the internal balance, violating encapsulation.
Output
true
You can also read What Is Command Line Crash Course Navigation In Ruby