Overview
The ternary operator (? :
) provides a compact way to write simple conditional expressions in Ruby. It evaluates a boolean condition and returns one of two values based on the result. The ternary operator follows the syntax condition ? value_if_true : value_if_false
and serves as a concise alternative to simple if-else statements.
Ruby's ternary operator is an expression that always returns a value, making it useful for variable assignments, method arguments, and return statements where you need conditional logic in a single line.
# Basic ternary operator usage
age = 20
status = age >= 18 ? "adult" : "minor"
puts status # => "adult"
# Equivalent if-else statement
if age >= 18
status = "adult"
else
status = "minor"
end
The ternary operator has the same precedence as the ||
and &&
operators and is right-associative, meaning nested ternary operators are evaluated from right to left.
Basic Usage
Simple Conditional Assignment
The most common use case involves assigning different values to a variable based on a condition:
temperature = 75
weather = temperature > 80 ? "hot" : "comfortable"
score = 85
grade = score >= 90 ? "A" : score >= 80 ? "B" : "C"
user_role = current_user.admin? ? "administrator" : "user"
Method Arguments and Return Values
The ternary operator works well for conditional method arguments and return values:
def greet(name, formal = false)
greeting = formal ? "Good day" : "Hi"
"#{greeting}, #{name}!"
end
def calculate_discount(amount, is_member)
amount * (is_member ? 0.9 : 1.0)
end
def format_price(cents)
cents > 0 ? "$#{cents / 100.0}" : "Free"
end
Inline Conditionals in String Interpolation
Ternary operators can be embedded within string interpolation for dynamic content:
items_count = 3
message = "You have #{items_count} item#{items_count == 1 ? '' : 's'} in your cart"
logged_in = true
nav_link = "#{logged_in ? 'Dashboard' : 'Login'}"
user = { name: "Alice", premium: true }
badge = "#{user[:name]}#{user[:premium] ? ' ⭐' : ''}"
Hash and Array Operations
Use ternary operators for conditional hash values or array operations:
config = {
environment: Rails.env.production? ? "prod" : "dev",
debug: Rails.env.development? ? true : false,
timeout: high_traffic ? 30 : 60
}
# Conditional array filtering
results = search_term.empty? ? all_records : filtered_records
Advanced Usage
Nested Ternary Operators
While nested ternary operators can handle multiple conditions, use them judiciously to maintain readability:
# Traffic light logic
def traffic_action(light_color)
light_color == "green" ? "go" :
light_color == "yellow" ? "caution" :
light_color == "red" ? "stop" : "unknown"
end
# Grade calculation with multiple thresholds
def letter_grade(score)
score >= 97 ? "A+" :
score >= 93 ? "A" :
score >= 90 ? "A-" :
score >= 87 ? "B+" :
score >= 83 ? "B" : "Below B"
end
# Complex conditional logic
def pricing_tier(user)
user.premium? ?
(user.annual? ? "premium_annual" : "premium_monthly") :
(user.trial? ? "trial" : "basic")
end
Method Chaining with Ternary Operators
Ternary operators can be combined with method chaining for concise conditional operations:
# Conditional method chaining
text = input.present? ? input.strip.downcase.capitalize : "Default"
# API response formatting
response = success ? data.to_json : { error: message }.to_json
# Conditional transformation pipeline
result = valid_email? ?
email.downcase.strip.gsub(/\s+/, '') :
default_email
Lambda and Proc Integration
Ternary operators work effectively with lambdas and procs for dynamic behavior:
# Conditional lambda selection
sorter = ascending ? ->(a, b) { a <=> b } : ->(a, b) { b <=> a }
sorted_array = array.sort(&sorter)
# Dynamic validation
validator = strict_mode ?
->(value) { value.present? && value.length >= 8 } :
->(value) { value.present? }
# Conditional callback assignment
callback = success ? method(:on_success) : method(:on_failure)
Class and Module Context
Use ternary operators in class definitions and module contexts:
class User
ROLE_MAPPINGS = Rails.env.production? ?
{ admin: 1, user: 2, guest: 3 } :
{ admin: 1, user: 2, guest: 3, developer: 0 }
def initialize(name, admin = false)
@name = name
@permissions = admin ? [:read, :write, :delete] : [:read]
end
def display_name
@name.length > 20 ? "#{@name[0..17]}..." : @name
end
end
Common Pitfalls
Operator Precedence Issues
The ternary operator has specific precedence rules that can cause unexpected behavior:
# Problematic: assignment has lower precedence
result = condition ? a = "yes" : a = "no" # Confusing
# Better: use parentheses
result = condition ? (a = "yes") : (a = "no")
# Best: separate assignment
a = condition ? "yes" : "no"
result = a
# Method call precedence
# This doesn't work as expected:
puts condition ? "true" : "false".upcase # => "false" or "FALSE"
# Should be:
puts condition ? "true" : ("false".upcase)
puts (condition ? "true" : "false").upcase
Nil and Falsy Values
Ruby's truthiness rules apply to ternary operators - only nil
and false
are falsy:
# Common mistake with zero, empty strings, empty arrays
count = 0
message = count ? "items available" : "no items" # => "items available"
# Should check for zero specifically:
message = count > 0 ? "items available" : "no items"
# Empty collections are truthy
array = []
status = array ? "has data" : "empty" # => "has data"
# Check for emptiness:
status = array.any? ? "has data" : "empty"
# Nil vs false distinction
def process_flag(flag)
# This treats nil and false the same:
flag ? "enabled" : "disabled"
# If nil means "not set" and false means "explicitly disabled":
flag.nil? ? "not set" : (flag ? "enabled" : "disabled")
end
Readability and Maintainability
Complex ternary operations can become difficult to read and maintain:
# Hard to read and debug
result = condition1 ? (condition2 ? value1 : condition3 ? value2 : value3) : value4
# Better: use explicit if-else for complex logic
if condition1
if condition2
result = value1
elsif condition3
result = value2
else
result = value3
end
else
result = value4
end
# Or extract to a method
def determine_result
return value4 unless condition1
return value1 if condition2
return value2 if condition3
value3
end
Side Effects in Ternary Expressions
Be cautious with expressions that have side effects in ternary operators:
# Problematic: both sides have side effects
result = condition ? increment_counter : decrement_counter
# Both methods get defined and could be called unexpectedly
# Better: use explicit conditional
if condition
increment_counter
else
decrement_counter
end
# File operations example
# Dangerous:
file = writable ? File.open("log.txt", "w") : File.open("log.txt", "r")
# Better:
mode = writable ? "w" : "r"
file = File.open("log.txt", mode)
Performance Considerations
Short-Circuit Evaluation
Ternary operators use short-circuit evaluation, only executing the chosen branch:
# Expensive operations are only called when needed
result = cache_hit? ? cached_value : expensive_calculation
# Database queries
user = logged_in? ? User.find(session[:user_id]) : nil
# File system operations
content = file_exists? ? File.read(path) : default_content
Memory Allocation Patterns
Consider memory allocation when using ternary operators with object creation:
# Both objects created regardless of condition (avoid)
result = condition ? expensive_object_a : expensive_object_b
# Better: only create needed object
result = if condition
expensive_object_a
else
expensive_object_b
end
# String allocation example
# Creates both strings:
message = error ? "Error: #{details}" : "Success: #{details}"
# Better for memory:
prefix = error ? "Error" : "Success"
message = "#{prefix}: #{details}"
Testing Strategies
Unit Testing Ternary Logic
Test both branches of ternary operators to ensure complete coverage:
RSpec.describe "user status" do
it "returns adult for users 18 and older" do
user = User.new(age: 18)
status = user.age >= 18 ? "adult" : "minor"
expect(status).to eq("adult")
end
it "returns minor for users under 18" do
user = User.new(age: 17)
status = user.age >= 18 ? "adult" : "minor"
expect(status).to eq("minor")
end
it "handles edge case at exactly 18" do
user = User.new(age: 18)
status = user.age >= 18 ? "adult" : "minor"
expect(status).to eq("adult")
end
end
Mocking Conditional Dependencies
Use mocking to test different ternary operator branches:
describe "pricing calculation" do
let(:user) { double("user") }
context "when user is premium" do
before { allow(user).to receive(:premium?).and_return(true) }
it "applies premium discount" do
price = user.premium? ? 100 * 0.8 : 100
expect(price).to eq(80)
end
end
context "when user is not premium" do
before { allow(user).to receive(:premium?).and_return(false) }
it "applies no discount" do
price = user.premium? ? 100 * 0.8 : 100
expect(price).to eq(100)
end
end
end
Integration Testing Complex Conditionals
Test ternary operators within the context of larger workflows:
feature "conditional navigation" do
scenario "admin user sees admin links" do
admin_user = create(:user, :admin)
login_as(admin_user)
visit dashboard_path
# Test ternary logic in view:
# <%= current_user.admin? ? "Admin Panel" : "User Dashboard" %>
expect(page).to have_content("Admin Panel")
expect(page).not_to have_content("User Dashboard")
end
scenario "regular user sees standard links" do
regular_user = create(:user)
login_as(regular_user)
visit dashboard_path
expect(page).to have_content("User Dashboard")
expect(page).not_to have_content("Admin Panel")
end
end
Reference
Syntax and Structure
Component | Description | Example |
---|---|---|
condition |
Boolean expression to evaluate | x > 5 , user.admin? , array.empty? |
? |
Separator between condition and true value | Required operator |
value_if_true |
Expression returned when condition is truthy | "yes" , calculate() , object.method |
: |
Separator between true and false values | Required operator |
value_if_false |
Expression returned when condition is falsy | "no" , default_value , nil |
Operator Precedence
Precedence Level | Operators | Associativity |
---|---|---|
Higher | [] , . , :: |
Left to right |
** |
Right to left | |
+ , - (unary) |
Right to left | |
* , / , % |
Left to right | |
+ , - |
Left to right | |
<< , >> |
Left to right | |
& |
Left to right | |
| , ^ |
Left to right | |
> , >= , < , <= , <=> , == , === , != , =~ , !~ |
Left to right | |
&& |
Left to right | |
|| |
Left to right | |
.. , ... |
Non-associative | |
Current | ? : |
Right to left |
Lower | = , += , -= , etc. |
Right to left |
Common Patterns
Pattern | Use Case | Example |
---|---|---|
Simple Assignment | Variable initialization | status = online? ? "active" : "inactive" |
Method Arguments | Conditional parameters | format_date(date, verbose ? :long : :short) |
Return Values | Method returns | def max(a, b); a > b ? a : b; end |
String Interpolation | Dynamic content | "#{count} item#{count == 1 ? '' : 's'}" |
Hash Values | Configuration objects | { timeout: production? ? 30 : 5 } |
Validation | Input checking | age = input.to_i > 0 ? input.to_i : 18 |
Nil Safety | Default values | name = user&.name ? user.name : "Anonymous" |
Truthiness Reference
Value | Truthy/Falsy | Ternary Result |
---|---|---|
true |
Truthy | Returns first value |
false |
Falsy | Returns second value |
nil |
Falsy | Returns second value |
0 |
Truthy | Returns first value |
"" |
Truthy | Returns first value |
[] |
Truthy | Returns first value |
{} |
Truthy | Returns first value |
Any object | Truthy | Returns first value |
Best Practices Summary
Practice | Recommendation | Reason |
---|---|---|
Line Length | Keep ternary expressions under 80 characters | Improves readability |
Nesting Limit | Avoid more than 2 levels of nesting | Prevents complexity |
Side Effects | Avoid expressions with side effects | Maintains predictability |
Parentheses | Use parentheses for complex expressions | Clarifies precedence |
Consistency | Use similar patterns across codebase | Improves maintainability |
Documentation | Comment complex ternary logic | Aids future developers |
Testing | Test both branches explicitly | Ensures complete coverage |