---
title: "Ruby 3 supports transforming hash keys using a hash argument"
description:
  "Ruby Hash#transform_keys now accepts a hash for transforming existing keys"
canonical_url: "https://www.bigbinary.com/blog/ruby-3-supports-transforming-hash-keys-using-a-hash-argument"
markdown_url: "https://www.bigbinary.com/blog/ruby-3-supports-transforming-hash-keys-using-a-hash-argument.md"
---

# Ruby 3 supports transforming hash keys using a hash argument

Ruby Hash#transform_keys now accepts a hash for transforming existing keys

- Author: Yedhin Kizhakkethara
- Published: December 15, 2020
- Categories: Ruby, Ruby 3

From Ruby 3 onwards, the `Hash#transform_keys` method accepts a hash argument
for transforming existing keys to new keys as specified in the argument.

##### Usage before Ruby 3

The following example shows how we used to apply `transform_keys`:

```ruby
# 1. Declare address hash
irb(main)> address = {House: 'Kizhakkethara', house_no: 123, locality: 'India'}
=> {:House=>"Kizhakkethara", :house_no=>123, :locality=>"India"}

# 2. Lowercase all the keys
irb(main)> address.transform_keys(&:downcase)
=> {:house=>"Kizhakkethara", :house_no=>123, :locality=>"India"}

# 3. Replace a particular key with a new key along with lowercasing
irb(main)* address.transform_keys do |key|
irb(main)*   new_key = key
irb(main)*   if key == :locality
irb(main)*     new_key = :country
irb(main)*   end
irb(main)*   new_key.to_s.downcase.to_sym
irb(main)> end
=> {:house=>"Kizhakkethara", :house_no=>123, :country=>"India"}
```

Although the changes required are trivial, we ended up writing a block to do the
job. But what happens when the number of keys that needs to be transformed
increases? Do we need to write n-number of conditions within a block? Not
anymore!

##### Introducing Hash#transform_keys with hash argument

Let's take the same example and provide a hash, which will be used for the
transformation:

```ruby
# 1. Declare address hash
irb(main)> address = {House: 'Kizhakkethara', house_no: 123, locality: 'India'}
=> {:House=>"Kizhakkethara", :house_no=>123, :locality=>"India"}

# 2. Provide hash with transform_keys
irb(main)> address.transform_keys({House: :house, locality: :country})
=> {:house=>"Kizhakkethara", :house_no=>123, :country=>"India"}
```

That does the job. But let's try to improve this code. Ultimately what happens
when we invoke that method is that it goes through each of the keys in our
variable and maps the existing keys to the new keys. The `transform_keys` method
accepts a block as a parameter. Thus let's pass in the `downcase` method as a
`Proc` argument:

```ruby
# 1. Passing in block parameters
irb(main)> address.transform_keys({locality: :country}, &:downcase)
=> {:house=>"Kizhakkethara", :house_no=>123, :country=>"India"}
```

An important point to be noted about the block parameter is that, **it's only
applied to keys which are not specified in the hash argument**.

### Other common use cases

##### Transforming params received in the Rails controller

```ruby
# 1. Declare params
irb(rails)> params = ActionController::Parameters.new({"firstName"=>"oliver", "lastName"=>"smith", "email"=>"oliver@bigbinary.com"})
=> <ActionController::Parameters {"firstName"=>"oliver", "lastName"=>"smith", "email"=>"oliver@bigbinary.com"} permitted: false>

# 2. Convert camelCase to snake_case using block parameter
irb(rails)> params.permit(:firstName, :lastName, :email).transform_keys(&:underscore)
=> <ActionController::Parameters {"first_name"=>"oliver", "last_name"=>"smith", "email"=>"oliver@bigbinary.com"} permitted: true>

# 3. Or using hash argument
irb(rails)> params.permit(:firstName, :lastName, :email).transform_keys({firstName: 'first_name', lastName: 'last_name'})
=> <ActionController::Parameters {"first_name"=>"oliver", "last_name"=>"smith", "email"=>"oliver@bigbinary.com"} permitted: true>
```

##### Slicing hash along with key transformation

```ruby
irb(main)> address.transform_keys({locality: :country}).slice(:house_no, :country)
=> {:house_no=>123, :country=>"India"}
```

##### Transforming keys in place using bang counterpart

```ruby
irb(main)> address.transform_keys!({locality: :country}, &:downcase)
irb(main)> address
=> {:house=>"Kizhakkethara", :house_no=>123, :country=>"India"}
```

##### References

- Discussions regarding this feature can be found
  [here](https://bugs.ruby-lang.org/issues/16274?tab=history).
- Commit for this feature can be found
  [here](https://github.com/ruby/ruby/commit/b25e27277dc39f25cfca4db8452d254f6cc8046e).

## Links

- [Human page](https://www.bigbinary.com/blog/ruby-3-supports-transforming-hash-keys-using-a-hash-argument)
