Labs ICT
โญ Pro Login

Access Control

public, private, and protected methods.

Access Control

Ruby has three access levels: public, private, and protected. Public methods can be called by anyone. Private methods can only be called within the object itself. Protected methods can be called by other instances of the same class. This gives you fine-grained control over your API.

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

  def deposit(amount)
    log_transaction("deposit", amount)
    @balance += amount
    @balance
  end

  def withdraw(amount)
    if amount > @balance
      puts "Insufficient funds"
      return
    end
    log_transaction("withdraw", amount)
    @balance -= amount
    @balance
  end

  def display
    "#{@owner}'s balance: $#{@balance}"
  end

  private

  def log_transaction(type, amount)
    puts "[LOG] #{type}: $#{amount}"
  end

  protected

  def balance
    @balance
  end
end

account = BankAccount.new("Alice", 1000)
puts account.deposit(500)   # => [LOG] deposit: $500
                             # => 1500
puts account.display         # => "Alice's balance: $1500"

# account.log_transaction("test", 10)  # => NoMethodError (private)
# account.balance                      # => NoMethodError (protected)
Try it Yourself ->

Private Methods

Private methods can't be called with an explicit receiver โ€” not even self. They're for internal implementation details that the outside world shouldn't need to know about. Ruby enforces this strictly.

class Calculator
  def compute(a, b)
    result = add(a, b)
    multiply(result, 2)
  end

  private

  def add(a, b)
    a + b
  end

  def multiply(a, b)
    a * b
  end
end

calc = Calculator.new
puts calc.compute(3, 5)  # => 16

# calc.add(1, 2)        # => NoMethodError: private method 'add'
# self.add(1, 2)        # => NoMethodError: private method 'add'
Try it Yourself ->

Protected Methods

Protected methods can be called on other instances of the same class. This is useful when you need to compare objects or share data between instances without exposing it to the outside world.

class Student
  include Comparable

  def initialize(name, grade)
    @name = name
    @grade = grade
  end

  def <=>(other)
    grade <=> other.grade
  end

  def to_s
    "#{@name}: #{@grade}"
  end

  protected

  attr_reader :grade
end

s1 = Student.new("Alice", 95)
s2 = Student.new("Bob", 87)

puts s1 > s2       # => true (protected grade works)
puts s1 <=> s2     # => 1

# s1.grade           # => NoMethodError (protected)
# But inside <=>, other.grade works because both are Students
Try it Yourself ->

attr_reader is Public

Accessor methods created with attr_reader, attr_writer, and attr_accessor are public by default. This is usually what you want โ€” your data should be readable. If you need read-only access, use attr_reader. If you need full control, write your own methods and make them private.

class Config
  attr_reader :name
  attr_accessor :debug

  def initialize(name)
    @name = name
    @debug = false
    @secret = "hidden"
  end

  def info
    "Config: #{name}, debug: #{debug}"
  end

  private

  attr_reader :secret
end

config = Config.new("production")
puts config.name    # => "production" (public)
config.debug = true # => public writer
puts config.debug   # => true

# config.secret     # => NoMethodError (private)
Try it Yourself ->

The Convention: Private Helpers

The Ruby convention is to make helper methods private. These are small utility methods that support your public API but shouldn't be called directly. This keeps your public interface clean and focused.

class Report
  def initialize(data)
    @data = data
  end

  def summary
    "Total: #{total}, Average: #{average}, Max: #{maximum}"
  end

  private

  def total
    @data.sum
  end

  def average
    total.to_f / @data.length
  end

  def maximum
    @data.max
  end

  def format_number(num)
    "%.2f" % num
  end
end

report = Report.new([10, 20, 30, 40, 50])
puts report.summary
# => "Total: 150, Average: 30.0, Max: 50"

# report.total      # => NoMethodError
# report.average    # => NoMethodError
Try it Yourself ->

๐Ÿงช Quick Quiz

What access level prevents external calls?