Ruby keyword: if
if expr then body [elsif expr then body]* [else body] end | body if expr Ruby’s if keyword picks a branch to run based on whether a test expression is truthy. Unlike the if you may have seen in C or Java, Ruby if is an expression, not a statement, which means every form returns a value you can assign, return, or pass around. That single fact changes how you read and write Ruby code.
Syntax forms
Every block form closes with end. The condition runs first; if it is truthy, the matching branch runs and its last expression becomes the value of the whole if expression. No version of if in Ruby returns “no value”. If no branch runs, the expression returns nil.
Simple if
age = 20
if age >= 18
"adult"
end
# => "adult"
When the test is false, the value of the expression is nil because no branch ran. There is no hidden else nil clause in the language, just the same default an empty method body would produce. That nil is what makes the no-else form safe to feed into assignments or use as a return value, and the next snippet assigns that exact case so you can see the value come out as nil rather than disappearing into a void you cannot inspect.
result = if 1 == 2
"matched"
end
result
# => nil
The two-branch form, where every possible case is covered by an else, is usually what you want for any value that will be assigned or returned. With only an if and no else, you have to remember that a false test produces nil, which can become a bug when the caller expects a string or a number. Adding else makes the fallback case explicit and removes the implicit nil.
if with else
age = 12
status = if age >= 18
"adult"
else
"minor"
end
status
# => "minor"
Two branches are enough for many real conditions, but the moment a value depends on more than one test the elsif chain becomes the natural extension. Each elsif clause carries its own expression, and the branches are tried in source order until one evaluates to true. The first match wins, and the rest are not even evaluated.
if/elsif/else chains
grade = 75
if grade >= 90
"A"
elsif grade >= 80
"B"
elsif grade >= 70
"C"
else
"F"
end
# => "C"
elsif is a single token. The first branch whose test is truthy wins; later branches are not evaluated. There is no limit on the number of elsif clauses, but in practice more than three or four usually means a case statement or lookup table would read better.
Ternary form
n = 7
parity = n.even? ? "even" : "odd"
parity
# => "odd"
The ternary is semantically identical to if/else as an expression. Reach for it only when both branches are short and obvious. Nested ternaries hurt readability fast.
Modifier if
n = 0
n += 1 if n.zero?
n
# => 1
Modifier if puts the body on the left and the test on the right. It is a statement form, which matters for the gotchas below.
# Also valid: log on a condition
puts "saved" if persisted?
Truthy and falsy values
Ruby’s truth rules are simple: nil and false are false. Everything else, including 0, "", [], and any object that does not explicitly opt out, is true. The control expressions page in the official docs is explicit about this.
if 0 then "yes" else "no" end
# => "yes"
if "" then "yes" else "no" end
# => "yes"
if nil then "yes" else "no" end
# => "no"
if false then "yes" else "no" end
# => "no"
if [1,2,3] then "yes" else "no" end
# => "yes"
A common consequence: an empty array is truthy, so if items does not check whether the array has any elements. The same rule applies to an empty string, the integer 0, and any object that has not been told to act as false. Only nil and false are false in Ruby; everything else returns true from the conditional test.
items = []
if items
puts "has items"
else
puts "empty"
end
# prints "has items" (empty array is still truthy)
Use items.empty? or items.any? to test for emptiness.
If expression return values
Because if is an expression, you can assign its result, return it from a method, or pass it to another method. The value is the last expression evaluated in the branch that ran, or nil if no branch ran.
def fee(age)
if age < 12
0
elsif age < 65
10
else
5
end
end
fee(8) # => 0
fee(30) # => 10
fee(70) # => 5
A method that would be a chain of return statements in other languages reads as a single value expression here. Once you internalize that if returns the value of its last branch, you stop reaching for temporary variables and explicit returns, and the method body becomes a single expression that picks the right value to hand back.
def label(score)
if score >= 90 then "A"
elsif score >= 80 then "B"
else "C"
end
end
label(85) # => "B"
Common gotchas
elsif is one word, not else if
else if (two words) does parse, but it is a nested if inside the else branch, not a chain. It works for simple cases and gets unwieldy fast.
# Works, but creates a nested if rather than a chain
if a
"a"
else
if b
"b"
else
"c"
end
end
Use elsif to keep the chain flat. The flat form uses one end keyword instead of two, which keeps the indentation shallow and lets the reader see the whole decision as one block rather than a series of nested if statements each closing separately. Both versions produce the same value, but elsif is the form you want almost every time a chain comes up in real code.
if a
"a"
elsif b
"b"
else
"c"
end
then is optional on a newline
then is the separator between the test and the body when they share a line. On its own line the body is unambiguously the next expression, so the separator becomes optional. Both forms compile to the same thing, but the newline form is what almost every Ruby style guide recommends, including the Ruby community style guide.
if x == 1 then puts "one" end
On a newline, then is legal but conventionally omitted.
Assignment in the test is idiomatic
Because = returns the assigned value, you can write the common find or 404 pattern cleanly:
if (user = User.find_by(id: params[:id]))
# user is guaranteed non-nil here
render json: user
else
render status: :not_found
end
Parens are optional here. if user = User.find_by(...) works the same. The test runs the assignment, and nil/false short-circuits to the else branch. The local variable user is then in scope in both the body and the else branch, which is what makes the pattern read so well.
Modifier if is a statement, not an expression
You cannot pass a modifier if directly to a method as an argument:
puts(1 if true) # SyntaxError
Wrap it in an extra pair of parens to make it an expression. The outer parens belong to puts; the inner parens force the parser to evaluate the modifier if as an expression that resolves to either the body or nil.
puts((1 if true))
# prints 1
This shows up most often when reaching for a one-liner puts and is a frequent surprise.
The modifier if assignment gotcha
Worth seeing once, because the parser-ordering trap is real:
p a if a = 0.zero?
# raises NameError: undefined local variable or method `a'
The parser scans left to right. The body p a tells it that a is a method call, so when the body runs, a is looked up as a method. The assignment in the test only marks a as a local in the surrounding scope, which is too late for the body’s lookup. Assign first, then test:
a = 0.zero?
p a if a
# prints true