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
%wto 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
%ito 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
ArrayorHashliteral, 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 nilsWhen accessing the first or last element from an array, prefer
firstorlastover[0]or[-1].Use
Setinstead ofArraywhen dealing with unique elements.Setimplements a collection of unordered values with no duplicates. This is a hybrid ofArray's intuitive inter-operation facilities andHash'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 }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 ofHash#has_key?andHash#value?instead ofHash#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_keyinstead ofHash#keys.eachandHash#each_valueinstead ofHash#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#fetchwhen 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#fetchas 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) # => falsePrefer the use of the block instead of the default value in
Hash#fetchif 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_atwhen 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')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[]onnil.# 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
nilbefore 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