Control Flow and Methods
Ruby's control flow reads close to English, and almost all of it produces a value you can assign.
scores = [92, 78, 85, 41, 67]
scores.each do |score|
grade = if score >= 90 then "A"
elsif score >= 80 then "B"
elsif score >= 60 then "C"
else "F"
end
puts format("%3d -> %s", score, grade)
end
Open this example in the editor and change it. Nothing you type is saved.
Note that if returned a value. So did the whole expression. That is why Ruby
code rarely assigns the same variable in four branches.
unless, and the modifier form
unless is if not, and reads better when the negative is the natural way to
say it. Any of these can be written after the statement they guard.
stock = 0
puts "out of stock" unless stock.positive?
def withdraw(balance, amount)
return "amount must be positive" unless amount.positive?
return "insufficient funds" if amount > balance
"withdrew #{amount}, #{balance - amount} left"
end
puts withdraw(100, 30)
puts withdraw(100, 300)
puts withdraw(100, -5)
Open this example in the editor and change it. Nothing you type is saved.
Those early returns are called guard clauses. They handle the wrong cases first and let the real work sit unindented at the bottom.
case, which compares with ===
case does not use ==. It uses ===, which each class defines for itself:
a Range asks "do you fall inside me", a Class asks "are you an instance of me",
a Regexp asks "do you match".
def describe(value)
case value
when 0 then "zero"
when 1..9 then "single digit"
when Integer then "a bigger integer"
when /\A[a-z]+\z/ then "lowercase word"
when String then "some other string"
when nil then "nothing"
else "a #{value.class}"
end
end
[0, 5, 1000, "hello", "Hello!", nil, 3.5].each do |v|
puts format("%-10s %s", v.inspect, describe(v))
end
Open this example in the editor and change it. Nothing you type is saved.
Loops, and why you will rarely write one
3.times { |i| print i, " " }
puts
1.upto(3) { |i| print i, " " }
puts
(1..10).step(3) { |i| print i, " " }
puts
# while and until exist, and are the right tool when the end condition is not
# a collection running out.
countdown = 3
until countdown.zero?
puts "t-minus #{countdown}"
countdown -= 1
end
# loop rescues StopIteration for you, which makes it the partner of an
# external iterator.
numbers = [10, 20, 30].each
loop { puts numbers.next }
puts "done"
Open this example in the editor and change it. Nothing you type is saved.
Methods
A method returns its last expression. return is for leaving early.
def area(width, height)
width * height
end
# Default arguments, and keyword arguments that are required unless defaulted.
def introduce(name, greeting: "Hello", punctuation: "!")
"#{greeting}, #{name}#{punctuation}"
end
puts area(3, 4)
puts introduce("Aisha")
puts introduce("Kenji", greeting: "Welcome", punctuation: ".")
# Keyword arguments are worth it the moment a call has a bare true in it:
# resize(image, 800, 600, true, false) tells the reader nothing.
def resize(width:, height:, preserve_ratio: true)
"#{width}x#{height} ratio=#{preserve_ratio}"
end
puts resize(width: 800, height: 600, preserve_ratio: false)
# Splat collects the rest; double splat collects keywords.
def log(level, *messages, **context)
"[#{level}] #{messages.join(' ')} #{context.inspect}"
end
puts log(:warn, "disk", "nearly full", device: "/dev/sda", pct: 91)
Open this example in the editor and change it. Nothing you type is saved.
The naming conventions carry meaning
Two suffixes are conventions the whole ecosystem follows.
words = ["b", "a", "c"]
sorted = words.sort # returns a new array
puts sorted.inspect
puts words.inspect # unchanged
words.sort! # ! means "this one mutates the receiver"
puts words.inspect
puts words.empty? # ? means "this returns true or false"
puts words.frozen?
# A one-line method can be written with =, which reads well for short ones.
def double(n) = n * 2
puts double(21)
Open this example in the editor and change it. Nothing you type is saved.
The ! does not mean "dangerous" on its own - it means "the surprising one of
a pair". sort! exists because sort exists.
What to take away
if,caseandbeginare expressions; they return values.- Guard clauses with
unless/ifmodifiers keep the happy path unindented. caseuses===, so ranges, classes and regexes all work as conditions.- Keyword arguments make a call site readable; use them instead of flags.
?means it answers a question,!means it is the mutating twin.