07 - Collections

  • Prefer literal array and hash creation notation (unless you need to pass parameters to their constructors, that is).

    # bad
    arr = Array.new
    hash = Hash.new
    
    # good
    arr = []
    hash = {}
    
  • Prefer %w to the literal array syntax when you need an array of words (non-empty strings without spaces and special characters in them). Apply this rule only to arrays with two or more elements.

    # bad
    STATES = ['draft', 'open', 'closed']
    
    # good
    STATES = %w(draft open closed)
    
  • Prefer %i to the literal array syntax when you need an array of symbols (and you don't need to maintain Ruby 1.9 compatibility). Apply this rule only to arrays with two or more elements.

    # bad
    STATES = [:draft, :open, :closed]
    
    # good
    STATES = %i(draft open closed)
    
  • Avoid comma after the last item of an Array or Hash literal, especially when the items are not on separate lines.

    # bad - easier to move/add/remove items, but still not preferred
    VALUES = [
               1001,
               2020,
               3333,
             ]
    
    # bad
    VALUES = [1001, 2020, 3333, ]
    
    # good
    VALUES = [1001, 2020, 3333]
    
  • Avoid the creation of huge gaps in arrays.

    arr = []
    arr[100] = 1 # now you have an array with lots of nils
    
  • When accessing the first or last element from an array, prefer first or last over [0] or [-1].

  • Use Set instead of Array when dealing with unique elements. Set implements a collection of unordered values with no duplicates. This is a hybrid of Array's intuitive inter-operation facilities and Hash's fast lookup.

  • Prefer symbols instead of strings as hash keys.

    # bad
    hash = { 'one' => 1, 'two' => 2, 'three' => 3 }
    
    # good
    hash = { one: 1, two: 2, three: 3 }
    
  • Avoid the use of mutable objects as hash keys.

  • Use the Ruby 1.9 hash literal syntax when your hash keys are symbols.

    # bad
    hash = { :one => 1, :two => 2, :three => 3 }
    
    # good
    hash = { one: 1, two: 2, three: 3 }
    
  • Don't mix the Ruby 1.9 hash syntax with hash rockets in the same hash literal. When you've got keys that are not symbols stick to the hash rockets syntax.

    # bad
    { a: 1, 'b' => 2 }
    
    # good
    { :a => 1, 'b' => 2 }
    
  • Use Hash#key? instead of Hash#has_key? and Hash#value? instead of Hash#has_value?. As noted here by Matz, the longer forms are considered deprecated.

    # bad
    hash.has_key?(:test)
    hash.has_value?(value)
    
    # good
    hash.key?(:test)
    hash.value?(value)
    
  • Use Hash#each_key instead of Hash#keys.each and Hash#each_value instead of Hash#values.each.

    # bad
    hash.keys.each { |k| p k }
    hash.values.each { |v| p v }
    hash.each { |k, _v| p k }
    hash.each { |_k, v| p v }
    
    # good
    hash.each_key { |k| p k }
    hash.each_value { |v| p v }
    
  • Use Hash#fetch when dealing with hash keys that should be present.

    heroes = { batman: 'Bruce Wayne', superman: 'Clark Kent' }
    # bad - if we make a mistake we might not spot it right away
    heroes[:batman] # => 'Bruce Wayne'
    heroes[:supermann] # => nil
    
    # good - fetch raises a KeyError making the problem obvious
    heroes.fetch(:supermann)
    
  • Introduce default values for hash keys via Hash#fetch as opposed to using custom logic.

    batman = { name: 'Bruce Wayne', is_evil: false }
    
    # bad - if we just use || operator with falsy value we won't get the expected result
    batman[:is_evil] || true # => true
    
    # good - fetch work correctly with falsy values
    batman.fetch(:is_evil, true) # => false
    
  • Prefer the use of the block instead of the default value in Hash#fetch if the code that has to be evaluated may have side effects or be expensive.

    batman = { name: 'Bruce Wayne' }
    
    # bad - if we use the default value, we eager evaluate it
    # so it can slow the program down if done multiple times
    batman.fetch(:powers, obtain_batman_powers) # obtain_batman_powers is an expensive call
    
    # good - blocks are lazy evaluated, so only triggered in case of KeyError exception
    batman.fetch(:powers) { obtain_batman_powers }
    
  • Use Hash#values_at when you need to retrieve several values consecutively from a hash.

    # bad
    email = data['email']
    username = data['nickname']
    
    # good
    email, username = data.values_at('email', 'nickname')
    
  • Rely on the fact that as of Ruby 1.9 hashes are ordered.

  • Do not modify a collection while traversing it.

  • When accessing elements of a collection, avoid direct access via [n] by using an alternate form of the reader method if it is supplied. This guards you from calling [] on nil.

    # bad
    Regexp.last_match[1]
    
    # good
    Regexp.last_match(1)
    
  • When providing an accessor for a collection, provide an alternate form to save users from checking for nil before accessing an element in the collection.

    # bad
    def awesome_things
      @awesome_things
    end
    
    # good
    def awesome_things(index = nil)
      if index && @awesome_things
        @awesome_things[index]
      else
        @awesome_things
      end
    end