How to organize hashes by property - ruby

Here is the hashes that will be processed:
{
"flatiron school bk" => {
:location => "NYC"
},
"flatiron school" => {
:location => "NYC"
},
"dev boot camp" => {
:location => "SF"
},
"dev boot camp chicago" => {
:location => "Chicago"
},
"general assembly" => {
:location => "NYC"
},
"Hack Reactor" => {
:location => "SF"
}
}
I need to organize these hashes by location, like this:
{ "NYC"=>["flatiron school bk", "flatiron school", "general assembly"],
"SF"=>["dev boot camp", "Hack Reactor"],
"Chicago"=>["dev boot camp chicago"]}
}

You can use each_with_object to combine in into new hash:
hash.each_with_object({}) do |(name, data), res|
(res[data[:location]] ||= []) << name
end
Explanation:
each_with_object
Iterates the given block for each element with an arbitrary object given, and returns the initially given object.
In this case name and data is key and value of each element in given hash.
In (res[data[:location]] ||= []) << name you get location, create array in result hash for given location (if it doesn't exist), then put key of input hash to it.

Related

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

Convert Array to JSON, with Same Key, in Ruby

I'm trying to parse an array of hashes, grab a value from a specific key, and output to json with one predefined key for each value - and I'm stuck.
Array:
[{:object_id=>"jon-59",
:name=>"jon-59-gw (8db8fcae-055a-4b35-9f8f-739b68c0bd5d)",
:client_handle=>nil,
:extended_attributes=>nil,
:appliances_summary=>
{:vm_version=>"5.5.3",
:vm_build_info=>"5.5.3-2135647"},
:hypervisor_assist=>false,
:allowed_actions=>
{:string=>
["Change Log Level",
"Edit Dns",
"Edit Syslog"]},
:edge_assist_id=>"0"},
{:object_id=>"jon-60",
:name=>"jon-60-gw (d63ddc45-gd3c-40c3-9046-e7afa996934a)",
:client_handle=>nil,
:extended_attributes=>nil,
:appliances_summary=>
{:vm_version=>"5.5.3",
:vm_build_info=>"5.5.3-2168697"},
:hypervisor_assist=>false,
:allowed_actions=>
{:string=>
["Change Log Level",
"Edit Dns",
"Edit Syslog"]},
:edge_assist_id=>"0"}]
Desired Output
{
"data":[
{ "{#JONNAME}":"jon-59-gw" },
{ "{#JONNAME}":"jon-60-gw"},
]
}
Where I'm at:
def jon_discover
jon_summary.sort_by { |jon| jon[:object_id] }.each do |jon|
name = jon[:name].slice!(/\A\S*/)
my_hash = {'{#JONNAME}' => name}
puts JSON.generate(my_hash)
end
end
The above returns:
{ "{#JONNAME}":"jon-59-gw" }
{ "{#JONNAME}":"jon-60-gw" }
But I don't know where to take it from here, or if I'm on the right track. How can I get this into the desired output?
Thanks, cheers!
This is too complex:
my_hash = {"{#JONNAME}" => "#{name}"}
Keep it simple:
my_hash = {JONNAME => name}
the data is for Zabbix low level discovery
Then use single-quotes instead of double quotes for the key and use the bare name for the value:
my_hash = {'{#JONNAME}' => name}
so it's more apparent that {# is not a typo.
Instead of:
"jon-60-gw (d63ddc45-gd3c-40c3-9046-e7afa996934a)".slice!(/\A\S*/) # => "jon-60-gw"
Use:
"jon-60-gw (d63ddc45-gd3c-40c3-9046-e7afa996934a)".split.first # => "jon-60-gw"
Putting it all together:
require 'json'
ary = [
{
:object_id => "jon-59",
:name => "jon-59-gw (8db8fcae-055a-4b35-9f8f-739b68c0bd5d)",
:client_handle => nil,
:extended_attributes => nil,
:appliances_summary =>
{
:vm_version => "5.5.3",
:vm_build_info => "5.5.3-2135647"
},
:hypervisor_assist => false,
:allowed_actions => {
:string => ["Change Log Level", "Edit Dns", "Edit Syslog"]
},
:edge_assist_id => "0"
},
{
:object_id => "jon-60",
:name => "jon-60-gw (d63ddc45-gd3c-40c3-9046-e7afa996934a)",
:client_handle => nil,
:extended_attributes => nil,
:appliances_summary => {
:vm_version => "5.5.3",
:vm_build_info => "5.5.3-2168697"
},
:hypervisor_assist => false,
:allowed_actions => {
:string => ["Change Log Level", "Edit Dns", "Edit Syslog"]
},
:edge_assist_id => "0"
}
]
Here's how to walk through the data:
data = ary.map{ |hash|
{
'{#JONNAME}' => hash[:name].split.first
}
}
Here's how to generate the JSON:
puts JSON[{'data' => data}]
# >> {"data":[{"{#JONNAME}":"jon-59-gw"},{"{#JONNAME}":"jon-60-gw"}]}
If you need it sorted:
puts JSON[{'data' => data.sort_by{ |s| s['{#JONNAME}'] }}]
# >> {"data":[{"{#JONNAME}":"jon-59-gw"},{"{#JONNAME}":"jon-60-gw"}]}

Map keys with the same name

A GET to an API endpoint I'm working with returns json with an inconsistent order of contacts, either
{"contacts"=>[
{"id"=>$UUID_0, "name"=>nil, "email"=>$EMAIL_0, "phone"=>$PHONE_0, "type"=>"foo"},
{"id"=>$UUID_1, "name"=>nil, "email"=>$EMAIL_1, "phone"=>$PHONE_1, "type"=>"bar"}
]}
or
{"contacts"=>[
{"id"=>$UUID_1, "name"=>nil, "email"=>$EMAIL_1, "phone"=>$PHONE_1, "type"=>"bar"},
{"id"=>$UUID_0, "name"=>nil, "email"=>$EMAIL_0, "phone"=>$PHONE_0, "type"=>"foo"}
]}
The "type" values are the only static objects in these responses, so I'd like to map this so that the contact types are keys containing the other pairs:
{
"foo"=>{"id"=>$UUID_0, "name"=>$NAME_0, "email"=>$EMAIL_0, "phone"=>$PHONE_0},
"bar"=>{"id"=>$UUID_1, "name"=>$NAME_1, "email"=>$EMAIL_1, "phone"=>$PHONE_1}
}
A solution is not obvious to me.
If you use Ruby on Rails, or at least ActiveSupport, you can try index_by instead of group_by: it won't put the values into arrays.
hash['contacts'].index_by {|r| r['type']}
=>
{
"bar" => {
"id" => "asdf",
"name" => nil,
"email" => "EMAIL_1",
"phone" => "PHONE_1",
"type" => "bar"
},
"foo" => {
"id" => "asdf",
"name" => nil,
"email" => "EMAIL_0",
"phone" => "PHONE_0",
"type" => "foo"
}
}
Hash[data['contacts'].map { |c| [c['type'], c] }]
This can be done with Enumerable#reduce:
hash['contacts'].reduce({}) {|m,c| m[c['type']] = c;m}
How it works:
An empty hash is the starting point.
The block is called once for each element in the contacts list. The block receives the hash that we're building as m and the current contact as c.
In the block, assign c to the hash based on its type and return the hash so far.
Final result is the last return value of the block.

Update activerecord relation given a hash of multiple entries

I'm quite new to Rails, so be gentle :)
I have the following models set-up:
class User
has_many :it_certificates, :class_name => 'UserCertificate'
class UserCertificate
belongs_to :skill
Given the following input (in JSON)
{
"certificates":[
{ // update
"id":1,
"name":"Agile Web Dev 2",
"entity":"Agile Masters!",
"non_it":false,
"date_items":{
"month":10,
"year":2012
},
"skill": {
"id":57
}
},
{ // create
"name":"Agile Web Dev 1",
"entity":"Agile Masters!",
"non_it":false,
"date_items":{
"month":10,
"year":2011
},
"skill": {
"id":58
}
}
]
}
How's the easiest way to update the information for the relation it_certificates?
I've been looking to update_all but it doesn't match my needs (it only updates given fields with the same value).
So I've been struggling around with the approach of iterating over each of these records and then update them one-by-one.
I mean struggling because it looks to me there are lots of things I have to care of when the idea of Rails is the opposite.
Thanks in advance!
So, here's my solution for now:
def self.update_from_hash(data, user_id)
self.transaction do
data.each do |certificate|
if certificate[:id] == nil
# create
if !self.create(
:name => certificate[:name],
:entity => certificate[:entity],
:user_id => user_id,
:non_it => certificate[:non_it],
:skill_id => certificate[:skill][:id],
:date => self.build_date_from_items(certificate[:date_items][:month], certificate[:date_items][:year])
)
raise ActiveRecord::Rollback
end
else
# update
if !self.update(certificate[:id], {
:name => certificate[:name],
:entity => certificate[:entity],
:non_it => certificate[:non_it],
:skill_id => certificate[:skill][:id],
:date => self.build_date_from_items(certificate[:date_items][:month], certificate[:date_items][:year])
})
raise ActiveRecord::Rollback
end
end
end
end
return true
end
It works, but I'm still expecting a more elegant solution :)

How to validate a complete form with RSpec and Capybara?

I'm writing a request test with RSpec and Capybara. I have a hash that maps form field names to expected values.
How can I check easily that each form field has the expected value?
So far, I'm doing this, but it's complex and unmaintainable. I'm also considering only two kind of input controls in this case (select boxes and the rest):
expected_data = {
"address" => "Fake st 123",
"city" => "Somewhere",
"email" => "whoknows#example.com",
"gender" => "Male",
"state" => "FL",
}
select_boxes = ["gender", "state"]
# check for the select boxes
expected_data.select {|k,v| select_boxes.include?(k)}.each do |name, expected_value|
page.has_select?(name, :selected_value => expected_value).should == true
end
# check for the input fields
expected_data.reject {|k,v| select_boxes.include?(k)}.values.each do |expected_value|
page.should have_css("input[value=\"#{expected_value}\"]")
end
Is there a gem or something to do this in one line?
I find the following far more maintainable:
describe "form" do
subject {page}
before { visit "/path/to/form" }
it { should have_field("address", :with => "Fake st 123") }
it { should have_select("gender", :selected => "Male") }
# And so on ...
end

Resources