Ruby Hash#transform_keys now accepts a hash that maps existing keys to new keys


Ruby 2.5 added the methods Hash#transform_keys and Hash#transform_keys! to transform keys of a hash.

Before Ruby 2.8

These methods require a block that is used to transform all the keys of the hash.

Example

2.6.5 :001 > hash = {name: "John", zip: 12345, verified: true}
=> {:name=>"John", :zip=>12345, :verified=>true}

# Returns the hash with transformed keys as per the given block
2.6.5 :002 > hash.transform_keys(&:upcase)
=> {:NAME=>"John", :ZIP=>12345, :VERIFIED=>true}

# The bang sibling of this method mutates the hash in place
2.6.5 :003 > hash.transform_keys!(&:upcase)
=> {:NAME=>"John", :ZIP=>12345, :VERIFIED=>true}
2.6.5 :004 >

In the example above, all the keys of the hash were transformed to uppercase.


After Ruby 2.8 (to be 3.0.0)

Hash#transform_keys and Hash#transform_keys! will now accept an optional hash argument.

The keys of this hash argument represent the existing keys in the hash, and the values represent the new keys.

This allows us to selectively change the keys of a hash like below.

Example

irb(main):001:0> hash = {name: "John", zip: 12345, verified: true}
irb(main):002:0> hash.transform_keys({name: :first_name, zip: :zipcode})
=> {:first_name=>"John", :zipcode=>12345, :verified=>true}
irb(main):003:0>

In the example above, the keys name and zip were transformed to first_name and zipcode. But the verified key remained the same as it wasn’t there in the hash argument.


Passing both an argument and a block together

The hash takes priority over the block when both are passed to the method. Hence, the block is applied only to the keys which were not specified in the hash argument.

Example

hash = {name: "John", zip: 12345, verified: true}
irb(main):002:0> hash.transform_keys({name: :first_name, zip: :zipcode}, &:upcase)
# Only :verified was changed to uppercase as it wasn't specified in the hash argument
=> {:first_name=>"John", :zipcode=>12345, :VERIFIED=>true}

In the example above, the keys name and zip were transformed to first_name and zipcode as specified by the hash argument. Also, the block passed was applied to the only residual key verified and got transformed to uppercase.