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
orHash
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
orlast
over[0]
or[-1]
.Use
Set
instead ofArray
when dealing with unique elements.Set
implements 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_key
instead ofHash#keys.each
andHash#each_value
instead 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#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')
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
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