# Idiomatic Ruby and Common Pitfalls — Ruby

Source: https://www.geekswithgeeks.com/en/ruby/p-idioms

> 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.

```ruby
# 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.

**Quiz:** What does the safe navigation operator in user&.address do?

- [x] 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.
