Editing yaml hashmap with ruby and rake - ruby

I have a yaml who's elements need to be filled by a user... sounds simple enough. I am attempting to read the file, print the keys, ask for a value, and store the updated file.
cnfg.yml:
thing:
something:
another_thing:
much_depth:
such_yml:
...
Here is my code thus far:
task :setup_cnfg do
config = YAML.load_file 'cnfg.yml'
config.each do |key, value|
puts key
value.each do |k, v|
print " #{k}: "
v = STDIN.gets.chomp() #STDIN is there due to some strange rake shenanigans
end
end
File.open('cnfg.yml','w') {|f| f.write config.to_yaml}
end
If I print 'v' after I capture the input it does show the intended value but if i print the hash afterwords all imputed values will be gone!
What would I do in order to correctly populate and store all of my data fields?

You should actually assign the new v back to the value hash:
task :setup_cnfg do
config = YAML.load_file 'cnfg.yml'
config.each do |key, value|
puts key
value.each do |k, _|
print " #{k}: "
value[k] = STDIN.gets.chomp()
end
end
File.open('cnfg.yml','w') {|f| f.write config.to_yaml}
end

Related

Print out elements in a hash table, Ruby

I have a hash table that looks like this:
hash =
"{\"url\":\"/system/message\",\"device\":\"UNKNOWN\",\"version\":\"1.0\",\"timestamp\":\"2018-08-28T11:16:29.516617Z\",\"object\":{\"timestamp\":\"2018-08-28T11:16:29.516490Z\",\"id\":9800,\"debug_level\":2,\"message\":\"Got new port configuration\"}}"
hash.each do |variable|
puts variable
end
This do not work
You'll need to first convert your string to a hash.
require 'json'
my_hash = JSON.parse(hash)
my_hash.each do |key, value|
puts value
end
You can also do the following:
require 'json'
values = JSON.parse(hash).map {|k,v| puts v }

Ruby search for super nested key from json response

I have a terribly nested Json response.
[[{:test=>[{:id=>1, :b=>{id: '2'}}]}]]
There's more arrays than that but you get the idea.
Is there a way to recursively search through and find all the items that have a key I need?
I tried using this function extract_list() but it doesn't handle arrays well.
def nested_find(obj, needed_keys)
return {} unless obj.is_a?(Array) || obj.is_a?(Hash)
obj.inject({}) do |hash, val|
if val.is_a?(Hash) && (tmp = needed_keys & val.keys).length > 0
tmp.each { |key| hash[key] = val[key] }
elsif val.is_a?(Array)
hash.merge!(obj.map { |v| nested_find(v, needed_keys) }.reduce(:merge))
end
hash
end
end
Example
needed_keys = [:id, :another_key]
nested_find([ ['test', [{id:1}], [[another_key: 5]]]], needed_keys)
# {:id=>1, :another_key=>5}
The following is not what I'd suggest, but just to give a brief alternative to the other solutions provided:
2.1.1 :001 > obj = [[{:test=>[{:id=>1, :b=>{id: '2'}}]}]]
=> [[{:test=>[{:id=>1, :b=>{:id=>"2"}}]}]]
2.1.1 :002 > key = :id
=> :id
2.1.1 :003 > obj.inspect.scan(/#{key.inspect}=>([^,}]*)[,}]/).flatten.map {|s| eval s}
=> [1, "2"]
Note: use of eval here is just for an example. It would fail/produce incorrect results on anything whose inspect value was not eval-able back to the same instance, and it can execute malicious code:
You'll need to write your own recursive handler. Assuming that you've already converted your JSON to a Ruby data structure (via JSON.load or whatnot):
def deep_find_value_with_key(data, desired_key)
case data
when Array
data.each do |value|
if found = deep_find_value_with_key value, desired_key
return found
end
end
when Hash
if data.key?(desired_key)
data[desired_key]
else
data.each do |key, val|
if found = deep_find_value_with_key(val, desired_key)
return found
end
end
end
end
return nil
end
The general idea is that given a data structure, you check it for the key (if it's a hash) and return the matching value if found. Otherwise, you iterate it (if it's an Array or Hash) and perform the same check on each of it's children.
This will find the value for the first occurrence of the given key, or nil if the key doesn't exist in the tree. If you need to find all instances then it's slightly different - you basically need to pass an array that will accumulate the values:
def deep_find_value_with_key(data, desired_key, hits = [])
case data
when Array
data.each do |value|
deep_find_value_with_key value, desired_key, hits
end
when Hash
if data.key?(desired_key)
hits << data[desired_key]
else
data.each do |key, val|
deep_find_value_with_key(val, desired_key)
end
end
end
return hits
end

Ruby: how to iterate over an array of hash items?

irb> pp config
[{"file"=>"/var/tmp"},
{"size"=>"1024"},
{"modified"=>"03/28/2012"}]
=> nil
In the code,
config.each do |d|
# then how to break d into (k, v)???
end
config.each do |items|
items.each do |key, value|
# e.g. key="file", value="/var/tmp", etc.
end
end
Just do
config.each do |hash|
(k,v),_ = *hash
end
Inspired by #Arup's answer, here's a solution that doesn't require a extra, unused variable in the parallel assignment:
config.each do |hash|
key, value = hash.to_a[0]
end
to_a converts the hash into the same kind of array that you would get by using splat *hash, but you can actually index the first element of the array (i.e. the first key/value pair) with [0] this way, while trying to do so with splat (*hash) generates a syntax error (at least in Ruby version 2.1.1):
>> k,v = (*hash)[0]
SyntaxError: (irb):4: syntax error, unexpected ')', expecting '='
k,v = (*x)[0]
^
from c:/RailsInstaller/Ruby1.9.3/bin/irb:12:in `<main>'
>>
Of course, depending on what you're going to do with the key and value variables, it might make your code shorter and more readable to use one of these standard block constructs:
config.each do |hash|
hash.each { |key,value| puts "#{key}: #{value}" }
end
# or
config.each do |hash|
hash.each do |key,value|
puts "#{key}: #{value}"
end
end

How to get the key of a hash

Right now I have a double hash called data like the following:
data [name][action]
ie
data = {"Mike" => {"Walked" => 13, "Ran" => 5}, "Steve" => {...}}
For this particular hash, I don't actually know the keys in the hash, I just want to iterate over it, like so:
data.each |item| do
#how to get the key name for item here?
puts item["Walked"].to_s
puts item["Ran"].to_s
end
I'd like to get the key so I can display it in a table beside the values.
You can iterate over a hash using:
data.each do |key, value|
end
You can use each with a key, value syntax, as described in the each documentation:
data.each do |key, values|
puts key.to_s
values.each do |value|
value.to_s
end
end
You could also use keys or values depending on what you wanted to achieve.
data.keys.each do |key|
puts key #lists all keys
end
data.values.each do |value|
puts value #lists all values
end
data.keys.first #first key
and so on.

How to access complex array of hashes

I have this to work with:
[
["app1", {"name"=>"name1", "path"=>"xyz.com/"}],
["app2", {"name"=>"name2", "path"=>"xyz.com/"}],
["app3", {"name"=>"name3", "path"=>"xyz.com/"}],
# etc.
]
I want to be able to access each name and path so I tried:
apps.each do |key, value|
value.each do |key, value|
puts value
end
end
but this returns an Enumerator. Any idea how I can do this?
apps = [["app1", {"name"=>"name1", "path"=>"https://xyz.com/"}], ["app2", {"name"=>"name2", "path"=>"https:/xyz.com/"}], ["app3", {"name"=>"name3", "path"=>"https://xyz.com/"}]]
apps.flatten.each do |t|
next unless t.class == Hash
next unless t.key?("name")
next unless t.key?("path")
puts t.inspect # now t is a hash that has both "name" and "path" keys - do what you want
end
This will handle even a bit more complex cases when you have different structure for different elements.
I think your first each loop is only looping over an array, so it would be:
apps.each do |app|
app.each do |key, value|
puts key # would be app1 in the first array
puts value["name"]
puts value["path"]
end
end
ar = [
["app1", {"name"=>"name1", "path"=>"xyz.com/"}],
["app2", {"name"=>"name2", "path"=>"xyz.com/"}],
["app3", {"name"=>"name3", "path"=>"xyz.com/"}]
]
#Get a specific app:
p ar.assoc("app2").last["name"]
#Get all names and paths
ar.each{|app| name, path = app.last["name"], app.last["path"]}

Resources