How to Access a Ruby Hash with both String and Symbol Keys
Sometimes, you receive a Hash key as a method parameter or via user input, and you want to make the Hash understand that key as-is, without worrying if the key is a string or a symbol. ActiveSupport's `HashWithIndifferentAccess` class lets you accomplish this in a convenient way.
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'] # nilTo 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.
Sign up for my newsletter
Let's learn to become better developers.