Write Software, Well

How to Access a Ruby Hash with both String and Symbol Keys

September 27, 2023

If you try to access a regular symbolized Hash in Ruby with a string key, it returns nil value.

framework = { name: 'Ruby on Rails', language: 'Ruby' }

framework[:name] # "Ruby on Rails"
framework['language'] # nil

To design a better method API, you may want to allow the users (or method callers) to pass any key, without worrying whether it has to be a string or a symbol.

To access the Hash without worrying about the type of the key, use the ActiveSupport::HashWithIndifferentAccess class provided in ActiveSupport library. It implements a hash where keys :name and "name" are considered to be the same.

require "active_support/hash_with_indifferent_access"

framework = ActiveSupport::HashWithIndifferentAccess.new
framework[:name] = 'Ruby on Rails'

puts framework[:name] # Ruby on Rails
puts framework['name'] # Ruby on Rails

You can also pass an existing Hash to the constructor to get a new instance:

require "active_support/hash_with_indifferent_access"

framework = { name: 'Ruby on Rails', language: 'Ruby' }
result = ActiveSupport::HashWithIndifferentAccess.new(framework)

puts result['name'] # Ruby on Rails
puts result[:language] # Ruby

If that long class name looks intimidating, don't worry, you don't have to initialize this class yourself. Instead, use the with_indifferent_access extension method.

require "active_support/core_ext/hash/indifferent_access"

framework = { name: 'Ruby on Rails', language: 'Ruby' }
result = framework.with_indifferent_access

puts result[:name] # Ruby on Rails
puts result['language'] # Ruby

The with_indifferent_access method is added by Active Support by monkey-patching the Hash class. It returns a new instance of the ActiveSupport::HashWithIndifferentAccess, initializing it with the current hash.

# lib/active_support/core_ext/hash/indifferent_access.rb

class Hash
def with_indifferent_access
ActiveSupport::HashWithIndifferentAccess.new(self)
end
end

This class supports all the existing hash API, so you can call any method you'd call on a regular Ruby hash.

A good real-world example is the params hash in Rails controllers, which can be accessed with either a string or a symbol.

params = ActionController::Parameters.new(name: "Ruby")

params[:name] # Ruby
params['name'] # Ruby

Behind the scenes, it uses the with_indifferent_access method as follows:

module ActionController
class Parameters
def initialize(parameters = {}, logging_context = {})
# ...

@parameters = parameters.with_indifferent_access
end
end
end

What about the performance?

If you're worried about performance, check out this Reddit post for a nuanced discussion: Why wouldn't you want a hash with indifferent access?

However, as the user stillness_still mentions:

I got 99 problems and the lookup time difference between a symbol and a string ain't one.

Well said, sir, well said.

Subscribe to get future posts via email (or grab the RSS feed)

If you have a comment, please email it to me.