Ruby: transform Hash-Keys - ruby

I have a Hash:
urls = [{'logs' => 'foo'},{'notifications' => 'bar'}]
The goal is to add a prefix to the keys:
urls = [{'example.com/logs' => 'foo'},{'example.com/notifications' => 'bar'}]
My attempt:
urls.map {|e| e.keys.map { |k| "example.com#{k}" }}
Then I get an array with the desired form of the keys but how can I manipulate the original hash?

If you want to "manually" transform the keys, then you can first iterate over your array of hashes, and then over each object (each hash) map their value to a hash where the key is interpolated with "example.com/", and the value remains the same:
urls.flat_map { |hash| hash.map { |key, value| { "example.com/#{key}" => value } } }
# [{"example.com/logs"=>"foo"}, {"example.com/notifications"=>"bar"}]
Notice urls are being "flat-mapped", otherwise you'd get an arrays of arrays containing hash/es.
If you prefer to simplify that, you can use the built-in method for for transforming the keys in a hash that Ruby has; Hash#transform_keys:
urls.map { |url| url.transform_keys { |key| "example.com/#{key}" } }
# [{"example.com/logs"=>"foo"}, {"example.com/notifications"=>"bar"}]

Use transform_keys.
urls = [{'logs' => 'foo'}, {'notifications' => 'bar'}]
urls.map { |hash| hash.transform_keys { |key| "example.com/#{key}" } }
# => [{"example.com/logs"=>"foo"}, {"example.com/notifications"=>"bar"}]
One question: are you best served with an array of hashes here, or would a single hash suit better? For example:
urls = { 'logs' => 'foo', 'notifications' => 'bar' }
Seems a little more sensible a way to store the data. Then, saying you did still need to transform these:
urls.transform_keys { |key| "example.com/#{key}" }
# => {"example.com/logs"=>"foo", "example.com/notifications"=>"bar"}
Or to get from your original array to the hash output:
urls = [{'logs' => 'foo'}, {'notifications' => 'bar'}]
urls.reduce({}, &:merge).transform_keys { |key| "example.com/#{key}" }
# => {"example.com/logs"=>"foo", "example.com/notifications"=>"bar"}
Much easier to work with IMHO :)

If you don't have access to Hash#transform_keys i.e. Ruby < 2.5.5 this should work:
urls.map{ |h| a = h.to_a; { 'example.com/' + a[0][0] => a[0][1] } }

Related

Nested json from ruby hash

Trying to build a json out of Ruby hash
require 'json'
temp_rides = {"rides": {}}
rides = {:lyft => "car", :scoot => "scooter", :blade => "helicopter"}
rides.each do |key, value|
temp_rides["rides"].push({"key" => "value"})
puts temp_rides
end
apparently it fails with undefined methodpush'` I believe I need to load the json object before append (based on my python background).
I am looking for a output something like this
{
"rides": {
"lyft": {
"type": "car"
},
"scoot": {
"type": "scooter"
},
"blade": {
"type": "helicopter"
}
}
}
This is an answer to the OP's original question. I have no interest in changing it to attempt to answer a moving target.
Code
def doit(rides)
{ rides: rides.slice_before { |k,_| k.to_s.match? /\Aride\d*\z/ }.
map { |a| hashify(a.flatten.drop(1)) }.
reduce(&:merge) }
end
def hashify(arr)
f,*rest = arr
return f if rest.empty?
{ f.to_sym=>hashify(rest) }
end
Examples
Here is an example of the use of the (recursive) helper method hashify:
hashify ["lyft", :type, "car"]
#=> {:lyft=>{:type=>"car"}}
We are given the hash rides:
rides = {:ride1=>"lyft", :type=>"car", :ride2=>"Scoot",
:type2=>"scooter", :ride3=>"blade", :type3=>"helicopter"}
doit rides
#=> {:rides=>{:lyft=>{:type=>"car"},
# :Scoot=>{:type2=>"scooter"},
# :blade=>{:type3=>"helicopter"}}}
Let's add some more key-value pairs to rides:
rides = {:ride1=>"lyft", :type=>"car", :color=>"blue",
:ride2=>"Scoot", :type2=>"scooter", :make=>"Vespa", :model=>"98",
:ride3=>"blade", :type3=>"helicopter"}
doit rides
#=> {:rides=>{:lyft=>{:type=>{:car=>{:color=>"blue"}}},
# :Scoot=>{:type2=>{:scooter=>{:make=>
# {:Vespa=>{:model=>"98"}}}}},
# :blade=>{:type3=>"helicopter"}}}
Explanation
The steps for the first example are as follows.
enum = rides.slice_before { |k,_| k.to_s.match? /\Aride\d*\z/ }
#=> #<Enumerator: #<Enumerator::Generator:0x00005a49d68217f0>:each>
We can see the elements that will be generated by this enumerator by converting it to an array.
enum.to_a
#=> [[[:ride1, "lyft"], [:type, "car"]],
# [[:ride2, "Scoot"], [:type2, "scooter"]],
# [[:ride3, "blade"], [:type3, "helicopter"]]]
Continuing,
a = enum.map { |a| hashify(a.flatten.drop(1)) }
#=> [{:lyft=>{:type=>"car"}},
# {:Scoot=>{:type2=>"scooter"}},
# {:blade=>{:type3=>"helicopter"}}]
h = a.reduce(&:merge)
#=> {:lyft=>{:type=>"car"}, :Scoot=>{:type2=>"scooter"},
# :blade=>{:type3=>"helicopter"}}
{ rides: h }
#=> <as above>
It gave you undefined method push because there is no such method for a hash.
temp_rides = {"rides": {}}
# This create a hash with `:rides` symbol as the key {:rides=>{}}
# To push an object into a hash. Use operator[]= or #store method
temp_rides[:rides][:key1] = 'value1'
temp_rides[:rides].store(:key2, 'value2')
A working example:
require 'json'
temp_rides = {"rides": {}}
rides = {:lyft => "car", :scoot => "scooter", :blade => "helicopter"}
rides.each_pair do |k, v|
temp_rides[:rides][k] = {:type => v}
end
puts JSON.pretty_generate(temp_rides)
This is actually pretty easy to do with a simple transform:
def rejig(rides)
rides.map do |name, type|
[ name, { type: type } ]
end.to_h
end
Where that will rework your structure into the desired shape.
Using it is easy:
require 'json'
rides = { lyft: "car", scoot: "scooter", blade: "helicopter" }
puts JSON.dump(rejig(rides))
# => {"lyft":{"type":"car"},"scoot":{"type":"scooter"},"blade":{"type":"helicopter"}}
require 'json'
temp_rides = {"rides": {}}
rides = {:lyft => "car", :scoot => "scooter", :blade => "helicopter"}
temp_rides[:rides].merge!(rides)
to get ruby hash output
temp_rides
to get json format output
JSON.dump temp_rides
require 'json'
rides = {:lyft => "car", :scoot => "scooter", :blade => "helicopter"}
transformed = {
'rides' => rides.inject({}) { |h,(k,v)| h[k] = { 'type' => v }; h }
}
JSON.dump(transformed)

Mongodb replacing dot (.) in key name while inserting document

MongoDb doesn't support keys with dot. I have a Ruby nested hash that has many keys with dot (.) character. Is there a configuration that can be used to specify a character replacement for . like an underscore _ while inserting such data to MongoDb
I'm using MongoDB with Ruby & mongo gem.
example hash is like below
{
"key.1" => {
"second.key" => {
"third.key" => "val"
}
}
}
If it isn't possible to use keys with . in Mongodb, you'll have to modify the input data :
hash = {
'key.1' => {
'second.key' => {
'third.key' => 'val.1',
'fourth.key' => ['val.1', 'val.2']
}
}
}
Transforming string keys
This recursive method transforms the keys of a nested Hash :
def nested_gsub(object, pattern = '.', replace = '_')
if object.is_a? Hash
object.map do |k, v|
[k.to_s.gsub(pattern, replace), nested_gsub(v, pattern, replace)]
end.to_h
else
object
end
end
nested_gsub(hash) returns :
{
"key_1" => {
"second_key" => {
"third_key" => "val.1",
"fourth_key" => [
"val.1",
"val.2"
]
}
}
}
Transforming keys and values
It's possible to add more cases to the previous method :
def nested_gsub(object, pattern = '.', replace = '_')
case object
when Hash
object.map do |k, v|
[k.to_s.gsub(pattern, replace), nested_gsub(v, pattern, replace)]
end.to_h
when Array
object.map { |v| nested_gsub(v, pattern, replace) }
when String
object.gsub(pattern, replace)
else
object
end
end
nested_gsub will now iterate on string values and arrays :
{
"key_1" => {
"second_key" => {
"third_key" => "val_1",
"fourth_key" => [
"val_1",
"val_2"
]
}
}
}
In mongoDB, there is no configuration to support dot in the key. You need to preprocess the JSON before inserting to MongoDB collection.
One approach is that you can replace the dot with its unicode equivalent U+FF0E before insertion.
Hope this helps.

How to loop inside a hash?

If I have a hash like the one below, and I want to loop over the second level keys.
Why does this fail?
hash["Element"].each do |id|
h[id] = hash[id]["Name"]
end
hash
{
"Element" => {
"499723" => {
"Name" => "A",
},
"499725" => {
"Name" => "B",
},
}
It fails because using .each on a Hash yields the tuple of key and value.
hash = {
"Element" => {
"499723" => {
"Name" => "A",
},
"499725" => {
"Name" => "B",
},
}
}
hash["Element"].each do |id|
p id
end
["499723", {"Name"=>"A"}]
["499725", {"Name"=>"B"}]
Therefore, you need to use
hash["Element"].each do |id, value|
# ...
end
If you don't need the value
hash["Element"].each do |id, _|
# ...
end
However, you can keep the value and access it directly
hash["Element"].each do |id, value|
h[id] = value["Name"]
end
A simple solution in your case is to use Enumberable#each_with_object in combination with the previous information:
hash["Element"].each_with_object({}) do |(id, value), acc|
acc[id] = value["Name"]
end
# => {"499723"=>"A", "499725"=>"B"}
hash["Element"].each.with_object({}) do |(id, subhash), result|
result[id] = subhash["Name"]
end

Merge an array of hashes that have the same key but different value and add the value

Hello i have a hash that loos similar to this
#receivers
=> [{:amount=>50, :email=>"user_02#example.com"},
{:amount=>50, :email=>"user_02#example.com"},
{:amount=>50, :email=>"user_02#example.com"},
{:amount=>100, :email=>"user_01#example.com"},
{:amount=>100, :email=>"user_01#example.com"}]
How do i make it look like this?:
#receivers
=> [{:amount=>150, :email=>"user_02#example.com"}
{:amount=>200, :email=>"user_01#example.com"}]
Thank you for your help.
You can calculate it like this:
#receivers.group_by { |e| e[:email] }
.map { |k, v| { amount: v.sum { |e| e[:amount] }, email: k } }
#=> [{:amount=>150, :email=>"user_02#example.com"},
# {:amount=>200, :email=>"user_01#example.com"}]

ruby building hash from url

In Ruby, what is an efficient way to construct a Hash from a request path like:
/1/resource/23/subresource/34
into a hash that looks like this:
{'1' => { 'resource' => { '23' => 'subresource' => { '34' => {} } } }
Thanks
path = "/1/resource/23/subresource/34"
path.scan(/[^\/]+/).inject(hash = {}) { |h,e| h[e] = {} }
hash
=> {"1"=>{"resource"=>{"23"=>{"subresource"=>{"34"=>{}}}}}}
A recursive solution seems like the simplest thing to do. This isn't the prettiest, but it works:
def hashify(string)
k,v = string.gsub(/^\//, '').split('/', 2)
{ k => v.nil? ? {} : hashify(v) }
end
There may be edge cases it doesn't handle correctly (probably are) but it satisfies the example you've given.

Resources