Skip to main content
โšก Calmops

public, private, and protected Methods in Ruby

Introduction

Ruby provides three levels of method visibility: public, private, and protected. These control who can call a method and from where. Getting visibility right is important for encapsulation โ€” hiding implementation details and exposing only what’s necessary.

public Methods

public is the default visibility. Public methods can be called by anyone โ€” from inside the class, from subclasses, or from external code.

class BankAccount
  def balance
    @balance
  end

  def deposit(amount)
    @balance += amount
  end
end

account = BankAccount.new
account.deposit(100)   # OK โ€” public method
puts account.balance   # OK โ€” public method

You can also explicitly mark methods as public, though it’s rarely necessary:

class BankAccount
  public

  def balance
    @balance
  end
end

private Methods

private methods can only be called from within the class itself, and only without an explicit receiver (you cannot write self.private_method or object.private_method).

class BankAccount
  def initialize(balance)
    @balance = balance
  end

  def withdraw(amount)
    if sufficient_funds?(amount)
      @balance -= amount
      puts "Withdrew #{amount}. New balance: #{@balance}"
    else
      puts "Insufficient funds."
    end
  end

  private

  def sufficient_funds?(amount)
    @balance >= amount
  end
end

account = BankAccount.new(100)
account.withdraw(50)   # => Withdrew 50. New balance: 50
account.sufficient_funds?(10)  # => NoMethodError: private method called

private with Explicit Receiver (Ruby 2.7+)

Since Ruby 2.7, you can call private methods with an explicit self. receiver inside the class:

class Foo
  def run
    self.helper  # OK in Ruby 2.7+
    helper       # always OK
  end

  private

  def helper
    "helping"
  end
end

But you still cannot call private methods from outside the object.

Defining private Methods Inline

Ruby 2.1+ allows the private keyword directly before a method definition:

class User
  def greet
    "Hello, #{formatted_name}"
  end

  private def formatted_name
    "#{@first_name} #{@last_name}".strip
  end
end

protected Methods

protected methods can be called from within the class and by instances of the same class (or subclasses). They are not accessible from outside.

The key use case is comparing two instances of the same class:

class Employee
  def initialize(name, salary)
    @name   = name
    @salary = salary
  end

  def higher_paid_than?(other)
    salary > other.salary  # calling protected method on another instance
  end

  protected

  def salary
    @salary
  end
end

alice = Employee.new("Alice", 80_000)
bob   = Employee.new("Bob",   70_000)

puts alice.higher_paid_than?(bob)  # => true
puts alice.salary                  # => NoMethodError: protected method called

Without protected, you’d have to make salary public (exposing it to everyone) or use instance variables directly (breaking encapsulation).

Visibility in Inheritance

Subclasses inherit visibility rules:

class Animal
  def speak
    sound  # calls private method
  end

  private

  def sound
    "..."
  end
end

class Dog < Animal
  private

  def sound
    "Woof"
  end
end

puts Dog.new.speak  # => "Woof"
puts Dog.new.sound  # => NoMethodError: private method

Protected methods are accessible in subclasses too:

class Shape
  def larger_than?(other)
    area > other.area
  end

  protected

  def area
    0
  end
end

class Circle < Shape
  def initialize(radius)
    @radius = radius
  end

  protected

  def area
    Math::PI * @radius ** 2
  end
end

small = Circle.new(3)
large = Circle.new(5)
puts large.larger_than?(small)  # => true

Changing Visibility

You can change the visibility of inherited or existing methods:

class MyString < String
  # Make a public method private
  private :upcase

  # Make a private method public
  public :freeze
end

s = MyString.new("hello")
s.upcase  # => NoMethodError: private method

Comparison Table

Feature public private protected
Called from outside the class Yes No No
Called from within the class Yes Yes Yes
Called on another instance of same class Yes No Yes
Inherited by subclasses Yes Yes Yes
Default visibility Yes No No

Practical Guidelines

  • Default to private for helper methods that support public methods
  • Use protected when you need to compare or share state between instances of the same class
  • Keep public interfaces small โ€” expose only what callers need
  • Avoid making everything public โ€” it makes refactoring harder and leaks implementation details
class Order
  def initialize(items)
    @items = items
  end

  # Public interface
  def total
    subtotal + tax
  end

  def summary
    "Order total: $#{total.round(2)}"
  end

  private

  # Implementation details โ€” free to change without breaking callers
  def subtotal
    @items.sum { |item| item[:price] * item[:quantity] }
  end

  def tax
    subtotal * 0.08
  end
end

order = Order.new([
  { price: 10.0, quantity: 2 },
  { price: 5.0,  quantity: 3 }
])

puts order.summary   # => Order total: $37.7
puts order.subtotal  # => NoMethodError: private method

Resources

Comments