Complex map by objects property in ruby - ruby

I'm trying to create different kind of structure from the array I currently have in my code.
I've got an array of objects (active directory objects) so lets say i got a from db :
a = [o1,o2,o3,o4,o5]
My object has property source_id and name which are the relevant properties.
I want to create structure like this (I want hash) from the data I have in my array :
objects = Hash.new { |hash, key| hash[key] = [] }
And this would be one example of how to put the data inside new structure:
a.each do |ob|
objects[ob.source_id] << {
:new => '',
:name => {:unformated => ob.name, :formatted => ob.format(:name)}
}
end
I'm trying to replicate the same structure and it's not working out in my case :
a.group_by(&:source_id).map do |k,v|
{
k=> {
{
:new => '',
:name => {:unformated => v.name, :formatted => ob.format(:name)}
}
}
}
end.reduce(:merge)
This is the error I get :
! #<NoMethodError: undefined method `name' for #<Array:0xae542b4>>

With group_by your values in the a.group_by(&:source_id).map are going to be arrays of elements sharing the same source_id and not individual elements.
The following code may do what you wish:
a.group_by(&:source_id).map do |k,v|
{
k => v.map do |e|
{
:new => '',
:name => {:unformated => e.name, :formatted => e.format(:name)}
}
end
}
end.reduce(:merge)

Heres a one liner to do the same
new_hash = a.each_with_object({}) { |o, hash| hash[o.source_id] = {:new => '', :unformatted => o.name, :formatted => o.format(:name)} }
This does require ruby 1.9.3 though

Related

Using splat arguments to fetch from multidimensional hash

I am unable to come up with a nice way to access a multidimensional Hash with supplied key names in a splat operator - any suggestions?
Example:
I am having a Hash like
{
'key' => 'value',
'some' => {
'other' => {
'key' => 'othervalue'
}
}
}
and a function definition def foo(*args)
I want to return foo('key') value and foo('some','other','key') othervalue. All I can come up with are rather long and ugly for loops with a lot of nil? checks, and am somehow sure I am missing a more ruby-ish way to do this nice and short. Any hints are appreciated.
Update
Using the reply by Patrick below, I came up with
def foo(hash, *args)
keys.reduce(hash, :fetch)
end
which works as I expect it to. Thanks!
In some other languages this is known as get_in, for example in Clojure and Elixir. Here's a functional-ish implementation in Ruby:
class Hash
def get_in(*keys)
keys.reduce(self, :fetch)
end
end
Usage:
h = {
'key' => 'value',
'some' => {
'other' => {
'key' => 'othervalue'
}
}
}
h.get_in 'some'
#=> {
# "other" => {
# "key" => "othervalue"
# }
# }
h.get_in 'some', 'other'
#=> {
# "key" => "othervalue"
# }
h.get_in 'some', 'other', 'key'
#=> "othervalue"

Accessing array within a hash within a hash (adding new values)

Thank you for the help in advance!
I'd like to add or push a value into my array which is currently within a person hash and task hash.
Look below at the add task section task.merge!
task = {
"person1" => {
:high => ["email", "phone","email2","talk","meeting"],
:mid => ["task1","task2"],
:low => ["task1","task2"] },
"person2" => {
:high => ["email", "phone","email2","talk","meeting"],
:mid => ["task1","task2"],
:low => ["task1","task2"] },
"person3" => {
:high => ["email", "phone","email2","talk","meeting"],
:mid => ["task1","task2"],
:low => ["task1","task2"] },
}
puts task["person1"][:high][0]
# create task
#create new person hash within task hash
#already have :high, :mid, & :low hash
# add task
#add a task to the array within either high,mid, or low
task.merge!("person1" => {:high => "#{new_task}")
# remove task
#delete method
task.merge! "person1" => {:high => "#{new_task}"}
You simply had an error there. I removed the parenthesis to make it maybe a little more obvious.

Add to existing hash inside of a class ruby

I am trying to create a hash that has a data structure like so:
hash = {
:a => { :a1 => "x", :a2: => "x", :a3 => "x" },
:b => { :b1 => "x", :b2: => "x", :b3 => "x" },
}
Inside of a class function. I am rather new to OO, so maybe I'm not understanding the variable scoping correctly.
Here is my code:
class Foo
# Control class args
attr_accessor :site, :dir
# Initiate our class variables
def initialize(site,dir)
#site = site
#dir = dir
##records = {}
#records = Hash.new { |h, k| h[k] = Hash.new }
end
def grab_from_it
line = %x[tail -1 #{#dir}/#{#site}/log].split(" ")
time = line[0, 5].join(" ")
rc = line[6]
host = line[8]
ip = line[10]
file = line[12]
#records = { "#{file}" => { :time => "#{time}", :rc => "#{rc}", :host => "#{host}", :ip => "#{ip}" } }
end
end
Main body:
foo = Foo.new(site,dir)
foo.grab_from_it
pp foo
sleep(10)
foo.grab_from_it
pp foo
It works and successfully creates a hash with my desired structure, but when I run again, it overwrites the existing hash. I want it to keep adding to it, so I can create a "running tab".
Replace the following line
#records = { "#{file}" => { :time => "#{time}", :rc => "#{rc}", :host => "#{host}", :ip => "#{ip}" } }
with
#records["#{file}"] = { :time => "#{time}", :rc => "#{rc}", :host => "#{host}", :ip => "#{ip}" }
Every time you call #records = {} the instance variable points to a new hash. Thus the initialization code in initialize has no effect. Rather than replace the initialized hash with a new one, you should add new entry to the existing hash, using the []= instance method of Hash.
BTW, you can use variable to refer to the string rather than creating a new one using string interpolation "#{variable}".
#records[file] = { :time => time, :rc => rc, :host => host, :ip => ip }
If you want the UPDATE behavior for both the first and the second layers of the hash, you can take a look at the Hash#update method.
#records[file].update({ :time => time, :rc => rc, :host => host, :ip => ip })

What is the most ruby-ish way of accessing nested hash values at arbitrary depths? [duplicate]

This question already has answers here:
How to avoid NoMethodError for missing elements in nested hashes, without repeated nil checks?
(16 answers)
Closed 7 years ago.
Given a hash such as:
AppConfig = {
'service' => {
'key' => 'abcdefg',
'secret' => 'secret_abcdefg'
},
'other' => {
'service' => {
'key' => 'cred_abcdefg',
'secret' => 'cred_secret_abcdefg'
}
}
}
I need a function to return service/key in some cases and other/service/key in other cases. A straightforward way is to pass in the hash and an array of keys, like so:
def val_for(hash, array_of_key_names)
h = hash
array_of_key_names.each { |k| h = h[k] }
h
end
So that this call results in 'cred_secret_abcdefg':
val_for(AppConfig, %w[other service secret])
It seems like there should be a better way than what I've written in val_for().
def val_for(hash, keys)
keys.reduce(hash) { |h, key| h[key] }
end
This will raise an exception if some intermediate key is not found. Note also that this is completely equivalent to keys.reduce(hash, :[]), but this may very well confuse some readers, I'd use the block.
%w[other service secret].inject(AppConfig, &:fetch)
appConfig = {
'service' => {
'key' => 'abcdefg',
'secret' => 'secret_abcdefg'
},
'other' => {
'service' => {
'key' => 'cred_abcdefg',
'secret' => 'cred_secret_abcdefg'
}
}
}
def val_for(hash, array_of_key_names)
eval "hash#{array_of_key_names.map {|key| "[\"#{key}\"]"}.join}"
end
val_for(appConfig, %w[other service secret]) # => "cred_secret_abcdefg"
Ruby 2.3.0 introduced a new method called dig on both Hash and Array that solves this problem entirely.
AppConfig.dig('other', 'service', 'secret')
It returns nil if the key is missing at any level.

How do I fetch multiple hash keys with a nested hash?

I'm playing around with nested hashes and I'm trying to figure out how to fetch multiple keys when my hash is a nested one:
imahash = { :id => { :name => "Alma", :email => "alma#mail.com" },
:stats => { :gender => "Female" },
:location => { :city => "Freeport", :state => "Maine" }
}
I know how to retrieve the nested value, and typing in the hash name will dump all the keys and values. But what I want to do is to fetch specific keys, such as :name and :gender only. Or :name and :city only.
Is this possible? Because from what I've found, it seems that you can only retrieve hash values for one key at a time or for all the keys at once.
My desired output would be something like:
=> { :id => { :name => "Alma" }, :location => { :city => "Freeport" } }
I presume you want to grab the values out in a tuple? You can make an array that contains whatever collection of values you want.
Try the following for name and city:
[imahash[:id][:name], imahash[:location][:city]]
=> ["Alma", "Freeport"]
Not exactly sure what you're asking here, but it seems like you're wanting to create a new hash from the bigger one.
To fetch specific keys like :name and :gender only
name_and_gender_hash = {
:name => imahash[:id][:name],
:gender => imahash[:stats][:gender]
}
would result in
{:name => "Alma", :gender => "female"}

Resources