Question
How can I idiomatically check in Ruby that discount is neither nil nor zero?
if discount != nil && discount != 0
# ...
end
Is there a clearer or more idiomatic way to write this condition?
Short Answer
You will learn how Ruby treats nil, false, and 0 in conditions, and how to write a concise, safe check for a numeric value that must be present and non-zero.
Concept
Ruby conditions use truthiness:
- Only
nilandfalseare falsy. - Everything else is truthy, including
0, empty strings (""), and empty arrays ([]).
That means this does not exclude zero:
if discount
# This runs when discount is 0.
end
For a numeric discount, you must explicitly test whether it is zero. Ruby numeric objects provide zero? and nonzero? for this purpose.
A concise modern option is:
if discount&.nonzero?
# discount exists and is not zero
end
nonzero? returns nil when the number is zero, and returns the number itself when it is non-zero. The safe-navigation operator (&.) prevents an error when discount is nil.
Mental Model
Think of an if statement as a door that opens only for truthy values.
In Ruby, nil and false are red stop signs, so the door stays closed. But 0 is not a stop sign in Ruby—it is a normal truthy value.
To reject zero, you need a second check: ask the number whether it is zero with zero?, or ask it whether it is non-zero with nonzero?.
Syntax and Examples
For a value expected to be numeric, these are common choices.
# Clear and compatible with older Ruby versions
if discount && !discount.zero?
puts "Apply the discount"
end
discount && ... works because Ruby does not evaluate the right side when discount is nil. This prevents calling zero? on nil.
# Concise modern Ruby
if discount&.nonzero?
puts "Apply the discount"
end
The safe-navigation operator returns nil if discount is nil. If it is 0, nonzero? also returns nil. Both cases make the if body skip.
discount = 15
puts "Apply" if discount&.nonzero?
# Prints: Apply
You can also write the original comparison more idiomatically as:
if !discount.nil? && discount != 0
# ...
end
However, discount && !discount.zero? or discount&.nonzero? better communicates that the value is expected to be a number.
Step by Step Execution
Consider this code:
discount = 10
if discount&.nonzero?
puts "Discount is valid"
end
Step by step:
discountcontains10.&.checks whetherdiscountisnil. It is not, so Ruby calls10.nonzero?.10.nonzero?returns10.10is truthy in Ruby.- The condition passes and Ruby prints
Discount is valid.
Now with zero:
discount = 0
puts "Discount is valid" if discount&.nonzero?
discountis notnil, so Ruby calls0.nonzero?.0.nonzero?returnsnil.nilis falsy, so nothing is printed.
Now with no value:
discount = nil
puts "Discount is valid" if discount&.nonzero?
discountisnil.&.does not callnonzero?.- The expression evaluates to
nil. - The condition is false, so nothing is printed.
Real World Use Cases
Checking for a present, non-zero number is useful when a zero value means “do not perform this action.”
- Shopping carts: apply a discount only when the discount amount is non-zero.
- Invoices: show a late fee only when its calculated amount is non-zero.
- Analytics: calculate a ratio only when a count is non-zero.
- API payloads: include an optional numeric setting only when it has a meaningful value.
- Batch jobs: skip a database update when the number of changes is zero.
Example:
shipping_credit = order.shipping_credit
if shipping_credit&.nonzero?
total -= shipping_credit
end
Real Codebase Usage
In real Ruby applications, developers often use a guard clause to handle invalid or irrelevant values early:
def apply_discount(total, discount)
return total unless discount&.nonzero?
total - discount
end
This avoids wrapping the main logic in a large if block.
When input may come from a form, JSON request, or environment variable, validate or convert it before using numeric methods:
def parse_discount(value)
Integer(value, exception: false)
end
raw_discount = params[:discount]
discount = parse_discount(raw_discount)
return "Invalid discount" if discount.nil?
return "No discount to apply" if discount.zero?
For domain rules, make the rule explicit. For example, a negative discount may be invalid even though it is non-zero:
return unless discount&.positive?
apply_discount(discount)
Use the condition that expresses the actual business rule: non-zero, positive, non-negative, or present.
Common Mistakes
Assuming 0 is false
In some languages, zero is falsy. In Ruby, it is truthy.
discount = 0
puts "Runs" if discount
This prints Runs.
Avoid it: explicitly use zero?, nonzero?, positive?, or another numeric predicate.
Calling zero? on nil
This raises an error:
discount = nil
puts "Valid" if !discount.zero?
# NoMethodError: undefined method `zero?' for nil
Avoid it: check first with discount && !discount.zero?, or use discount&.nonzero?.
Using != nil unnecessarily
if discount != nil
# ...
end
This works, but Ruby developers typically write:
if discount
# ...
end
When false must be distinguished from nil, use nil? explicitly. For a numeric discount, false is usually not a valid value and should be validated separately if it can occur.
Using nonzero? with non-numeric input
discount = "10"
discount&.nonzero?
# NoMethodError: undefined method `nonzero?' for "10"
Avoid it: convert external input to a number before testing it.
discount = Integer("10", exception: false)
puts "Valid" if discount&.nonzero?
Comparisons
| Expression | nil | 0 | 10 | Best use |
|---|---|---|---|---|
if discount | false | true | true | Check only whether a value is truthy |
if discount && !discount.zero? | false | false | true | Clear numeric check; broadly compatible |
if discount&.nonzero? | false | false | true | Concise numeric check in modern Ruby |
if !discount.nil? && discount != 0 | false | false | true | Explicit, but more verbose |
if discount&.positive? | false | false | true | Require a value greater than zero |
zero? returns true or false:
0.zero? # true
5.zero? # false
nonzero? returns the number or nil:
0.nonzero? # nil
5.nonzero? # 5
Because a non-zero number is truthy, nonzero? fits naturally in an if condition.
Cheat Sheet
# Present and non-zero numeric value
if discount && !discount.zero?
# ...
end
# Equivalent concise form (modern Ruby)
if discount&.nonzero?
# ...
end
# Require a positive value, not merely non-zero
if discount&.positive?
# ...
end
# Run code unless the value is nil or zero
unless discount.nil? || discount.zero?
# ...
end
- Ruby falsy values:
nilandfalseonly. 0is truthy in Ruby.zero?andnonzero?are numeric methods.- Use
&.when the receiver may benil. - Convert untrusted strings to numbers before calling numeric predicates.
FAQ
Is 0 false in Ruby?
No. In Ruby, 0 is truthy. Only nil and false are falsy.
What is the most idiomatic Ruby check for nil and zero?
For a numeric value, discount&.nonzero? is concise. discount && !discount.zero? is also clear and widely used.
Why does nonzero? work directly in an if statement?
It returns nil for zero and returns the numeric receiver for a non-zero value. nil is falsy, while a number is truthy.
Should I use discount != nil in Ruby?
Usually no. Use if discount when any truthy value is acceptable. Use discount.nil? only when you specifically need to test for nil.
What happens if discount is false?
discount&.nonzero? returns false, because safe navigation only short-circuits for nil; it then evaluates as falsy in if. If false is possible input, validate the input type explicitly.
Can I use nonzero? with a string such as "0"?
No. Convert it first, for example with Integer(value, exception: false) or Float(value, exception: false).
Should a discount be non-zero or positive?
Use nonzero? when negative values are meaningful in your domain. Use positive? when only values greater than zero are valid.
Mini Project
Description
Build a small method that calculates an order total after applying an optional discount. It demonstrates how to ignore a missing or zero discount while keeping the business rule readable.
Goal
Return the original total for a nil or zero discount, and subtract a non-zero discount.
Requirements
- Create a method named
total_after_discount. - Accept
totalanddiscountas arguments. - Return
totalunchanged whendiscountisnilor zero. - Subtract the discount when it is non-zero.
- Demonstrate the method with nil, zero, and non-zero discounts.
Keep learning
Related questions
Add a Default Value to an Existing Column in a Rails Migration
Learn how to add or change a default value on an existing database column using Rails Active Record migrations safely.
Calling a Class Method from an Instance in Ruby
Learn how to call a class method from an instance in Ruby using self.class, with examples, pitfalls, and practical usage patterns.
Calling an Overridden Monkey-Patched Method in Ruby
Learn how to call the original method when monkey patching in Ruby, including alias_method patterns, examples, pitfalls, and practical usage.