December 15, 2020
This blog is part of our Ruby 3.0 series.
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.
The following example shows how we used to apply transform_keys
:
# 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!
Let's take the same example and provide a hash, which will be used for the transformation:
# 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:
# 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.
# 1. Declare params
irb(rails)> params = ActionController::Parameters.new({"firstName"=>"oliver", "lastName"=>"smith", "email"=>"[email protected]"})
=> <ActionController::Parameters {"firstName"=>"oliver", "lastName"=>"smith", "email"=>"[email protected]"} 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"=>"[email protected]"} 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"=>"[email protected]"} permitted: true>
irb(main)> address.transform_keys({locality: :country}).slice(:house_no, :country)
=> {:house_no=>123, :country=>"India"}
irb(main)> address.transform_keys!({locality: :country}, &:downcase)
irb(main)> address
=> {:house=>"Kizhakkethara", :house_no=>123, :country=>"India"}
If this blog was helpful, check out our full blog archive.