I suggest the following:
cars.map { |h| h[:properties] }.each_with_object({}) { |g,h|
h.update(g[:type]=>[{ name: g[:name] }]) { |_,o,n| {name: o+n } } }
#=> {"petrol"=>{:name=>[{:name=>"audi"}, {:name=>"ford"}]},
# "electric"=>[{:name=>"tesla"}]}
Firstly, we obtain:
cars.map { |h| h[:properties] }
#=> [{:name=>"audi", :type=>"petrol"},
# {:name=>"ford", :type=>"petrol"},
# {:name=>"tesla", :type=>"electric"}]
We then use the form of Hash#update (aka merge!) that uses the block:
{ |_,o,n| { name: o+n } }
to determine the values of keys that are present in both hashes being merged.
Initially, the hash:
{ "petrol"=>[{ :name=>"audi"] }] }
is merged into an empty hash represented by the block variable h. This merge does not make use of the block because h is empty and therefore does not have a key "petrol".
Next, the hash:
{ "petrol"=>[{ :name=>"ford" }] }
is merged into h. Since both this hash and h have keys "petrol", the value of "petrol" is given by the block:
{ |_,o,n| { name: o+n } }
#=> {|_,[{ :name=>"audi" }] , [{ :name=>"ford" }]|
# [{ name: "audi" }] + [{ name: "ford" }] }
#=> [{ name: "audi" }, { name: "ford" }]
Lastly, the hash:
{ "electric"=>{ :name=>"tesla" } }
is merged into h. Since h does not have a key "electric", the block is not not needed to determine the value of that key.