Lesson 24 / 25
Idiomatic Ruby and Common Pitfalls
Write idiomatic Ruby and avoid frequent mistakes.
Writing Ruby like Rubyists
Idiomatic Ruby favours readability: guard clauses (return unless valid?), unless for simple negatives, &. (the safe navigation operator) for possibly-nil chains (user&.address&.city), ||= for memoisation (@total ||= compute_total, with the caveat that it recomputes when the cached value is nil or false), Enumerable methods instead of manual loops, each_with_object for building hashes, keyword arguments for options, small methods and objects, and predicate and bang naming conventions. Common pitfalls: forgetting that 0 and "" are truthy; mutating arguments or default values unexpectedly (strings and arrays are references, so def add(item, list = []) creates a new array each call but mutating a passed-in array changes the caller's); confusing ==, eql? and equal?; rescuing Exception; overusing metaprogramming and monkey patching; deep inheritance instead of composition; using ^/$ in validations; and ignoring frozen string literal warnings. Follow the community style guide and let RuboCop catch the mechanical issues.
Unidiomatic versus idiomatic
The same behaviour written two ways.
# unidiomatic
def city_names(orders)
result = []
for o in orders
if o[:city] != nil && o[:city] != ""
if !result.include?(o[:city].capitalize)
result.push(o[:city].capitalize)
end
end
end
return result
end
# idiomatic
def city_names(orders)
orders.filter_map { |o| o[:city]&.capitalize unless o[:city].to_s.empty? }.uniq
end
class Report
def initialize(orders) = @orders = orders
def total
@total ||= @orders.sum { _1[:total] } # memoised
end
def revenue_by_city
@orders.each_with_object(Hash.new(0)) { |o, acc| acc[o[:city]] += o[:total] }
end
end
puts city_names([{ city: "pune" }, { city: nil }, { city: "pune" }, { city: "delhi" }]).inspect # ["Pune", "Delhi"]||= and falsy values
@flag ||= expensive_check recomputes every time if the result is false or nil. For values that can legitimately be falsy, use return @flag if defined?(@flag) before computing.
Quick check: What does the safe navigation operator in user&.address do?
- Returns nil instead of calling address when user is nil
- Raises if user is nil
- Creates a new user
- Freezes the user object
Answer
Returns nil instead of calling address when user is nil — &. short-circuits to nil when the receiver is nil.