Private Setters
Part of the Object Oriented Programming section of Coddy's Ruby journey. Lesson 19 of 57.
Often a value should be readable by everyone but changeable only by the object. attr_reader gives the public reader, and private attr_writer a setter that only the object can use. This works because attr_writer returns the names of the methods it defines:
class Wallet
attr_reader :balance
def initialize
self.balance = 0
end
def earn(amount)
self.balance = balance + amount
end
private attr_writer :balance
end
w = Wallet.new
w.earn(30)
puts w.balance
begin
w.balance = 1_000_000
rescue NoMethodError => e
puts e.message
endOutput:
30
private method `balance=' called for an instance of WalletA private setter is called with self. inside the class, the one receiver Ruby accepts for private methods. Without self. the assignment would only create a local variable:
class Lamp
attr_reader :on
def initialize
self.on = false
end
def toggle
self.on = !on
self
end
private
attr_writer :on
end
puts Lamp.new.toggle.toggle.toggle.onOutput:
trueGoing through a private setter instead of assigning @balance directly keeps one place for rules, such as a check that the value is valid:
class Tank
attr_reader :litres
def initialize(capacity)
@capacity = capacity
self.litres = 0
end
def fill(amount)
self.litres = litres + amount
end
def drain(amount)
self.litres = litres - amount
end
private
def litres=(value)
@litres = value.clamp(0, @capacity)
end
end
t = Tank.new(50)
t.fill(80)
puts t.litres
t.drain(70)
puts t.litresOutput:
50
0private attr_accessor :history hides both the reader and the setter, for internal state that outside code should not even see:
class Editor
def initialize
self.history = []
end
def type(text)
history << text
end
private attr_accessor :history
endChallenge
EasyComplete the class Wallet. balance is readable from outside but set only through a private setter, starting at 0. earn(amount) adds to it. spend(amount) subtracts and returns true, or returns false without a change when the balance is too low. history returns the number of successful changes, also kept behind a private setter.
The supplied code reads commands +n or -n, printing balance 30 after each, or declined when a spend fails. Then it prints changes: 2 and tries wallet.balance = 1000000, printing cannot set balance when Ruby refuses.
Your code goes in wallet.rb. main.rb holds the supplied input/output code and cannot be edited.
Try it yourself
require_relative 'wallet'
# Supplied input/output code: keep it as it is
input = []
while (line = gets)
input << line.chomp
end
wallet = Wallet.new
input.each do |cmd|
amount = cmd[1..].to_i
if cmd.start_with?("+")
wallet.earn(amount)
puts "balance #{wallet.balance}"
elsif wallet.spend(amount)
puts "balance #{wallet.balance}"
else
puts "declined"
end
end
puts "changes: #{wallet.history}"
begin
wallet.balance = 1_000_000
puts "balance set"
rescue NoMethodError
puts "cannot set balance"
end
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Classes and Objects
Working With FilesDefining ClassesInstance VariablesMethods and selfto_s and inspectRecap - Library Card7Polymorphism and Duck Typing
PolymorphismDuck TypingAbstract Methodscase and ClassesRecap - Payment Methods2Accessors and Self
Readers and WritersCustom Settersself in MethodsIdentity and EqualityRecap - Contact List8Special Methods
Equality and hashOperator MethodsIndexing and AppendingCustom CollectionsRecap - Fractions11Project: Library Management
Books and MembersBorrowing Books3Class Methods and Variables
Class MethodsClass Instance VariablesConstantsFactory MethodsRecap - Coupon CodesPractice on your own: Online Ruby compiler