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