Ruby hashes can have keys of any type, but Strings and Symbols are the most common.
For symbol keys there is a special syntax (similar to Javascript's object literal syntax).
{ "this is a string key" => 3 }
{ :symbol_key => 3 }
# is the same as
{ symbol_key: 3 }
Whether your hash has symbol keys or string keys is not always clear! Many sources of hashes (e.g. HTTP parameter parsing, the database, and others) may provide either.
If you receive a hash that your code did not make and want to get a value, make sure you write a test!
Many errors arise from code like:
# rando_hash == { "very_important_key" => true }
unless rando_hash[:very_important_key]
# This is executed when we didn't want it to be!
do_something_scary
end
ActiveSupport can help us out here with HashWithIndifferentAccess
which lets you treat Symbols and Strings interchangably if they have the same string value. The only
trick is knowing when you have one of these, but if you're not sure and definitely want one, you can make any
hash in Rails into a HashWithIndifferentAccess
# rando_hash == { "very_important_key" => true }
indifferent_rando_hash = rando_hash.with_indifferent_access
unless indifferent_rando_hash[:very_important_key]
# Phew! Now we don't execute this!
do_something_scary
end
Many times when you're using a hash to count things or group things, it's easier just to have a value like 0 or a new empty array initialized as the value for each key. Ruby has just the thing for this, but it might not be clear what's happening when you first see it.
hash_for_counting_stuff = Hash.new(0)
objects_with_dates.each do |object|
hash_for_counting_stuff[object.happened_at.month] += 1
end
# or with each_with_object
hash_for_counting_stuff =
objects_with_dates.each_with_object(Hash.new(0)) do |object, hash|
hash[object.happened_at.month] += 1
end
# the block is executed for every new key referenced
#
# h is the hash itself
# k is the new key
#
# this sets the value of the new key to a _new_ array.
#
hash_for_grouping_stuff = Hash.new { |h, k| h[k] = [] }
#
# This is valid ruby but never what you want!
#
# hash_for_grouping_stuff = Hash.new([])
#
# See this post for more details:
#
# https://medium.com/klaxit-techblog/a-headache-in-ruby-hash-default-values-bf2706660392
#
objects_with_dates.each do |object|
hash_for_grouping_stuff[object.happened_at.month] << object
end
Passing arguments to a method has a special syntax in Ruby where you can pass some of the final parameters as key value pairs that are collected into a hash and passed to the method.
def my_awesome_method(key, hash)
hash[key]
end
# this is a method call with our 2 parameters
my_awesome_method(:hiya, whats: :happening, hiya: :yall)
# returns :yall
# this is the same thing, but more explicit
my_awesome_method(:hiya, { whats: :happening, hiya: :yall })
# returns :yall
Be careful when you have keyword arguments! They're not the same as hashes, but the syntax can be the same from the calling side!
def my_keyword_method(
i_need_this_to_be_provided:,
this_one_is_optional: nil,
this_one_has_a_default: "hi"
)
[
i_need_this_to_be_provided,
this_one_is_optional,
this_one_has_a_default
].compact.join(" ")
end
# raises because a keyword is missing
my_keyword_method
# fine and defaults are provided for unspecified params
my_keyword_method(i_need_this_to_be_provided: "ok here it is")
# this raises because my_random_key isn't a keyword specified in the method definition
# and the arguments are not a hash!
my_keyword_method(
i_need_this_to_be_provided: "ok here it is",
my_random_key: "let's dance!"
)