Guarding State
Part of the Object Oriented Programming section of Coddy's Ruby journey. Lesson 20 of 57.
An object should refuse requests that would break its state. raise stops the method before anything changes. Your own error class names the problem precisely; < StandardError makes the class a kind of error (inheritance, chapter 5):
class InsufficientFunds < StandardError
end
class Account
attr_reader :balance
def initialize(balance)
@balance = balance
end
def withdraw(amount)
raise InsufficientFunds, "only #{balance} available" if amount > balance
@balance -= amount
end
end
account = Account.new(50)
begin
account.withdraw(80)
rescue InsufficientFunds => e
puts "#{e.class}: #{e.message}"
end
puts account.balanceOutput:
InsufficientFunds: only 50 available
50Separate error classes let the caller react to each problem differently, with one rescue per class. Declare error classes before the classes that raise them:
class InvalidAmount < StandardError
end
class LimitReached < StandardError
end
class Card
def charge(amount)
raise InvalidAmount, "amount must be positive" unless amount.positive?
raise LimitReached, "limit is 100" if amount > 100
"charged #{amount}"
end
end
[40, -5, 500].each do |amount|
puts Card.new.charge(amount)
rescue InvalidAmount => e
puts "fix the input: #{e.message}"
rescue LimitReached => e
puts "try later: #{e.message}"
endOutput:
charged 40
fix the input: amount must be positive
try later: limit is 100freeze turns an object read-only: after it, any change to its instance variables raises FrozenError, while reading still works. frozen? tells whether an object is frozen:
class Point
attr_accessor :x
def initialize(x)
@x = x
end
end
pt = Point.new(1).freeze
puts pt.frozen?
begin
pt.x = 2
rescue FrozenError => e
puts e.class
end
puts pt.xOutput:
true
FrozenError
1A reader that returns an internal array hands out the real array, so outside code could change it. Return a frozen copy with @items.dup.freeze, or @items.dup, to keep the object in control of its own data:
class Playlist
def initialize
@songs = []
end
def add(song)
@songs << song
end
def songs
@songs.dup.freeze
end
end
list = Playlist.new
list.add("Intro")
begin
list.songs << "Hack"
rescue FrozenError
puts "songs are read-only"
end
p list.songsOutput:
songs are read-only
["Intro"]Challenge
EasyDeclare the error classes InvalidAmount and InsufficientFunds, both StandardError subclasses, and complete Account. deposit(amount) and withdraw(amount) raise InvalidAmount with amount must be positive for amounts below 1. withdraw raises InsufficientFunds with balance is only 20 when the amount is larger than the balance. Nothing changes when an error is raised.
The supplied code reads commands dep n or wd n and prints balance 70 after each, invalid: amount must be positive for InvalidAmount and refused: balance is only 20 for InsufficientFunds.
Your code goes in invalid_amount.rb, insufficient_funds.rb and account.rb. main.rb holds the supplied input/output code and cannot be edited.
Try it yourself
require_relative 'invalid_amount'
require_relative 'insufficient_funds'
require_relative 'account'
# Supplied input/output code: keep it as it is
input = []
while (line = gets)
input << line.chomp
end
account = Account.new
input.each do |line|
action, amount = line.split
begin
action == "dep" ? account.deposit(amount.to_i) : account.withdraw(amount.to_i)
puts "balance #{account.balance}"
rescue InvalidAmount => e
puts "invalid: #{e.message}"
rescue InsufficientFunds => e
puts "refused: #{e.message}"
end
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