Hash with Indifferent Access

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.

2 min read

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.


That's a wrap. I hope you liked this article and you learned something new.

As always, if you have any questions or feedback, didn't understand something, or found a mistake, please leave a comment below or send me an email. I reply to all emails I get from developers, and I look forward to hearing from you.

If you'd like to receive future articles directly in your email, please subscribe to my blog. If you're already a subscriber, thank you.