how to return specific set of data from from array of hashes - ruby-on-rails

I have following data contract available in constant variable data
[
{
id: 1,
name: "class1",
start_at: "2017-08-15T10:00:00.000Z",
end_at: "2017-08-15T10:30:00.000Z",
},
{
id: 2,
name: "class2",
start_at: "2017-08-15T10:00:00.000Z",
end_at: "2017-08-15T10:30:00.000Z",
},
......more data here.....
]
I want to return the specific set of data.
e.g data.select {|e| e[:id] = 1} should return following but instead it returns all data.
[
{
id: 1,
name: "class1",
start_at: "2017-08-15T10:00:00.000Z",
end_at: "2017-08-15T10:30:00.000Z",
}
]
Any idea what is wrong?

extracted_data = data.select {|e| e[:id] == 1}
== for comparison

Related

Merge the value of same key names in ruby on rails

I want to merge the ids of same name values
hashes = [{
id: 3456824,
name: 'John'
},{
id: 6578954,
name: 'Vicky'
},{
id: 987456,
name: 'John'
}]
Expected:
[{
id: [3456824,987456],
name: 'John'
},{
id: 6578954,
name: 'Vicky'
}]
how I can achieve this in ruby on rails?
Here is a one liner:
hashes = [{
id: 3456824,
name: 'John'
},{
id: 6578954,
name: 'Vicky'
},{
id: 987456,
name: 'John'
}]
result = hashes.group_by{|h| h[:name] }.map{|k, v| {id: v.map{|x| x[:id]}, name: k}}
puts result
Check this repl: https://repl.it/repls/ShadowyCornyVideogames
Here are two ways to compute the desired result.
Use the form of Hash::new that takes a block
hashes.each_with_object(Hash.new { |h,k| h[k] = [] }) do |g,h|
h[g[:name]] << g[:id]
end.map { |name,id| { id: id, name: name } }
#=> [{:id=>[3456824, 987456], :name=>"John"},
# {:id=>[6578954], :name=>"Vicky"}]
The first step of this calculation1 is
hashes.each_with_object(Hash.new { |h,k| h[k] = [] }) do |g,h|
h[g[:name]] << g[:id]
end
#=> {"John"=>[3456824, 987456], "Vicky"=>[6578954]}
If a hash is defined
h = Hash.new { |h,k| h[k] = [] }
and (possibly after having added key-value pairs) h has no key k, h[k] in
h[k] << v
causes the block { |h,k| h[k] = [] } to be executed, resulting in the key value pair k=>[] being added to h, then << v is executed, changing h[k] from [] to [k].
Notice that this returns :id=>[6578954], rather than :id=>6578954, which was asked for by the question. Having all values of :id return an array avoids the need to check if :id returns an array or integer in subsequent code that processes the return value of this operation.
If :id=>6578954, were desired, one could write
hashes.each_with_object(Hash.new { |h,k| h[k] = [] }) do |g,h|
h[g[:name]] << g[:id]
end.transform_values { |v| v.size==1 ? v.first : v }.
map { |name,id| { id: id, name: name } }
#=> [{:id=>[3456824, 987456], :name=>"John"},
# {:id=>6578954, :name=>"Vicky"}]
See Hash#transform_values.
Use the form of Hash#update (a.k.a. merge!) that employs a block to determine the values of keys that are present in both hashes being merged
arr.each_with_object({}) do |g,h|
h.update(g[:name]=>[g[:id]]) { |_,o,n| o+n }
end.map { |name,id| { id: id, name: name } }
#=> [{:id=>[3456824, 987456], :name=>"John"},
# {:id=>[6578954], :name=>"Vicky"}]
If :id=>6578954, rather than :id=>[6578954], were desired:
arr.each_with_object({}) do |g,h|
h.update(g[:name]=>g[:id]) { |_,o,n| [*o,n] }
end.map { |name,id| { id: id, name: name } }
#=> [{:id=>[3456824, 987456], :name=>"John"},
# {:id=>6578954, :name=>"Vicky"}]
Notice that here update's argument is g[:name]=>g[:id] whereas it was previously g[:name]=>[g[:id]].
The first step is as follows.
arr.each_with_object({}) do |g,h|
h.update(g[:name]=>g[:id]) { |_,o,n| [*o,n] }
end
#=> {"John"=>[3456824, 987456], "Vicky"=>6578954}
In general, one or both of these approaches can be taken when Enumerable#group_by can be used. The reverse if often true also. The choice among these methods is a matter of personal taste.
1. A variant of the first part of this calculation is hashes.each_with_object({}) { |g,h| (h[g[:name]] ||= []) << g[:id] } #=> {"John"=>[3456824, 987456], "Vicky"=>[6578954]}.
You can do it like this:
args = [{
id: 3456824,
name: 'John'
},{
id: 6578954,
name: 'Vicky'
},{
id: 987456,
name: 'John'
}]
value_pairs = args.map { |h| h.values_at(:name, :id) }
grouped_by_name = value_pairs.group_by(&:first).transform_values { |arr| arr.map(&:last) }
as_hashes = grouped_by_name.map { |name, ids| { id: ids, name: name } }
One more possible solution is:
array = [
{
id: 3456824,
name: "John"
},
{
id: 6578954,
name: "Vicky"
},
{
id: 987456,
name: "John"
}
]
grouped_by_name = array.each_with_object(Hash.new {|h,k| h[k] = [] }) do |hash, result|
result[hash[:name]] << hash[:id]
end
=> {"John"=>[3456824, 987456], "Vicky"=>[6578954]}
grouped_by_name.map do |grouped_hash|
{
id: grouped_hash.last,
name: grouped_hash.first
}
end
=> [{:id=>[3456824, 987456], :name=>"John"}, {:id=>[6578954], :name=>"Vicky"}]

Merge a single array into nested array in rails

I have a list of objects something like this:
a = [
{
id: 0,
name: "ABC",
work: "ABC2"
},
{
id: 0,
name: "XYZ",
work: "XYZ2"
},
{
id: 1,
name: "ACD",
work: "ACD2"
}
]
And I want to convert it into something like this:
b = [
{
id: 0,
fields: [
{
name: "ABC",
work: "ABC2"
},
{
name: "XYZ",
work: "XYZ2"
}
]
},
{
id: 1,
fields: [
{
name: "ACD",
work: "ACD2"
}
]
}
]
The idea is to group the objects (by id) in one array.
The approach I tried is:
b = []
rest_object = []
a.each_with_index do |aa, idx|
aa.delete(:id)
rest_object << aa
if idx == 0
next
end
puts a[idx][:id], a[idx-1][:id]
if a[idx][:id] != a[idx-1][:id]
b << {id: a[idx-1][:id], names: rest_object}
rest_object = []
end
end
But I am getting an empty output.
Also, if it is possible to achieve the same in some cleaner way.
That would be helpful.
Something like this does the job. This deletes the :id key-value pair from each hash and uses the value to group the remainder of the hash. Then map the resulting hash to created an array and transform the data into {id: ..., fields: ...} format.
a = [{id: 0, name: "ABC", work: "ABC2"}, {id: 0, name: "XYZ", work: "XYZ2"}, {id: 1, name: "ACD", work: "ACD2"}]
b = a.group_by { |hash| hash.delete(:id) }
.map { |id, fields| {id: id, fields: fields} }
#=> [{:id=>0, :fields=>[{:name=>"ABC", :work=>"ABC2"}, {:name=>"XYZ", :work=>"XYZ2"}]}, {:id=>1, :fields=>[{:name=>"ACD", :work=>"ACD2"}]}]
Note: This mutates the hashes in the a array. If you don't want this change a.group_by to a.map(&:dup).group_by. Which first duplicates all hashes before doing any mutations.
Try following,
required_keys = a[0].except(:id).keys
b = a.group_by { |x| x[:id] }
b = b.inject([]) do |m,(k,v)|
arr = { id: k }
required_keys.each do |key|
arr[key.to_s.pluralize.to_sym] = v.map { |z| z.slice(key) }
end
m << arr
end
# => [{:id=>0, :names=>[{:name=>"ABC"}, {:name=>"XYZ"}]}, {:id=>1, :names=>[{:name=>"ACD"}]}]
a = [
{
id: 0,
name: "ABC"
},
{
id: 0,
name: "XYZ"
},
{
id: 1,
name: "ACD"
}
]
ary = []
a.each do|val|
ary[val[:id]] = {id: val[:id]} unless ary[val[:id]]
ary[val[:id]][:names] = [] unless ary[val[:id]][:names]
ary[val[:id]][:names].push({name: val[:name]})
end
If I understand, given a more general array like this:
a = [ { id: 0, name: "ABC", etc: "01" },
{ id: 0, name: "XYZ", etc: "02" },
{ id: 1, name: "ACD", etc: "11" } ]
The first idea I came up with is doing something like this:
require 'active_support/inflector' # for using pluralize
res = a.group_by{ |h| h[:id] }.values.map do |ary|
h = Hash.new{ |h,k| h[k] = [] }
ary.each { |hh| hh.each { |k,v| h[k] << v } }
h[:id] = h[:id].first
h.transform_keys do |k|
unless k == :id
k.to_s.pluralize.to_sym
else
k
end
end
end
res #=> [{:id=>0, :names=>["ABC", "XYZ"], :etcs=>["01", "02"]}, {:id=>1, :names=>["ACD"], :etcs=>["11"]}]
This is not exactly the format you require, but to get that format, just change this line
ary.each { |hh| hh.each { |k,v| h[k] << v } }
to
ary.each { |hh| hh.each { |k,v| h[k] << { k => v } } }

How to create loop within a JSON request object?

I need to create multiple "items" to be used within a json request .
Here's a request that works:
customs_info = EasyPost::CustomsInfo.create(
eel_pfc: 'NOEEI 30.37(a)',
customs_certify: true,
customs_signer: "first_name last_name",
contents_type: 'merchandise',
contents_explanation: '',
restriction_type: 'none',
restriction_comments: '',
# non_delivery_option: 'abandon',
customs_items: [
{
description: "asdf",
quantity: 1,
weight: 23,
value: 23,
# hs_tariff_number: '654321',
origin_country: 'US'
},
{
description: "1234568",
quantity: 1,
weight: 23,
value: 23,
# hs_tariff_number: '654321',
origin_country: 'US'
}
]
)
What I need is to not need to manually set the customs_items.
I tried:
customs_info = EasyPost::CustomsInfo.create(
eel_pfc: 'NOEEI 30.37(a)',
customs_certify: true,
customs_signer: "#{shipping_address.shipping_address_final.first_name} #{shipping_address.shipping_address_final.last_name}",
contents_type: 'merchandise',
contents_explanation: '',
restriction_type: 'none',
restriction_comments: '',
# non_delivery_option: 'abandon',
customs_items: [
vendor_line_items.map do |li|
{
description: "#{li.shop_product.product.item.title}",
quantity: li.quantity,
weight: li.shop_product.product.weight,
value: li.shop_product.price,
# hs_tariff_number: '654321',
origin_country: 'US'
}
end
]
)
Error Statement: Parameters to create Custom Item(s) invalid or missing
How can I create the loop to work with the JSON request and work like the first example that works manually?
If you remove the [] that is surrounding the vendor_line_itmes.map code, you will be good to go.
customs_info = EasyPost::CustomsInfo.create(
# ...
customs_items: vendor_line_items.map do |li|
{
description: "#{li.shop_product.product.item.title}",
quantity: li.quantity,
# ...
}
end
)
The map operation returns an array so the json you are currently generating would look like (note the array of arrays in customs_info):
{
"eel_pfc": "NOEEI 30.37(a)",
...
"customs_info": [
[
{
"description": "...",
"quantity": 5,
...
}
]
]
}

Create a deep nested hash using loops in Ruby

I want to create a nested hash using four values type, name, year, value. ie, key of the first hash will be type, value will be another hash with key name, then value of that one will be another hash with key year and value as value.
The array of objects I'm iterating looks like this:
elements = [
{
year: '2018',
items: [
{
name: 'name1',
value: 'value1',
type: 'type1',
},
{
name: 'name2',
value: 'value2',
type: 'type2',
},
]
},
{
year: '2019',
items: [
{
name: 'name3',
value: 'value3',
type: 'type2',
},
{
name: 'name4',
value: 'value4',
type: 'type1',
},
]
}
]
And I'm getting all values together using two loops like this:
elements.each do |element|
year = element.year
element.items.each |item|
name = item.name
value = item.value
type = item.type
# TODO: create nested hash
end
end
Expected output is like this:
{
"type1" => {
"name1" => {
"2018" => "value1"
},
"name4" => {
"2019" => "value4"
}
},
"type2" => {
"name2" => {
"2018" => "value2"
},
"name3" => {
"2019" => "value3"
}
}
}
I tried out some methods but it doesn't seems to work out as expected. How can I do this?
elements.each_with_object({}) { |g,h| g[:items].each { |f|
h.update(f[:type]=>{ f[:name]=>{ g[:year]=>f[:value] } }) { |_,o,n| o.merge(n) } } }
#=> {"type1"=>{"name1"=>{"2018"=>"value1"}, "name4"=>{"2019"=>"value4"}},
# "type2"=>{"name2"=>{"2018"=>"value2"}, "name3"=>{"2019"=>"value3"}}}
This uses the form of Hash#update (aka merge!) that employs a block (here { |_,o,n| o.merge(n) } to determine the values of keys that are present in both hashes being merged. See the doc for definitions of the three block variables (here _, o and n). Note that in performing o.merge(n) o and n will have no common keys, so a block is not needed for that operation.
Assuming you want to preserve the references (unlike in your desired output,) here you go:
elements = [
{
year: '2018',
items: [
{name: 'name1', value: 'value1', type: 'type1'},
{name: 'name2', value: 'value2', type: 'type2'}
]
},
{
year: '2019',
items: [
{name: 'name3', value: 'value3', type: 'type2'},
{name: 'name4', value: 'value4', type: 'type1'}
]
}
]
Just iterate over everything and reduce into the hash. On the structures of known shape is’s a trivial task:
elements.each_with_object(
Hash.new { |h, k| h[k] = Hash.new(&h.default_proc) } # for deep bury
) do |h, acc|
h[:items].each do |item|
acc[item[:type]][item[:name]][h[:year]] = item[:value]
end
end
#⇒ {"type1"=>{"name1"=>{"2018"=>"value1"},
# "name4"=>{"2019"=>"value4"}},
# "type2"=>{"name2"=>{"2018"=>"value2"},
# "name3"=>{"2019"=>"value3"}}}

Rails: How to merge two hashes if a specific key has the same value?

I'm trying to merge hashes if a specific key has the same value.
here is the array
[{
id: 77,
member_phone: "9876543210",
created_at: "2017-05-03T11:06:03.000Z",
name: "Sure"
},
{
id: 77,
member_phone: "123456789",
created_at: "2017-05-03T11:06:03.000Z",
name: "Sure"
},
{
id: 78,
member_phone: "12345",
created_at: "2017-05-03T11:06:03.000Z",
name: "XYZ"
}]
and the required output:
[{
id: 77,
member_phone: "123456789,9876543210",
created_at: "2017-05-03T11:06:03.000Z",
name: "Sure"
},
{
id: 78,
member_phone: "12345",
created_at: "2017-05-03T11:06:03.000Z",
name: "XYZ"
}]
here's the code I tried:
merge_users.group_by { |h1| h1["id"] }.map do |k,v|
{ "id" => k, :member_phone => v.map { |h2| h2[:member_phone] }.join(", ") }
end
how can I do it?
The following code would work for your given example.
code
result = arr.group_by {|h| h[:id]}.values.map do |arr|
arr.reduce do |h1, h2|
h1.merge(h2) do |k, ov, nv|
ov.eql?(nv) ? ov : [ov, nv].join(",")
end
end
end
p result
#=>[{:id=>77, :member_phone=>"9876543210,123456789", :created_at=>"2017-05-03T11:06:03.000Z", :name=>"Sure"}, {:id=>78, :member_phone=>"12345", :created_at=>"2017-05-03T11:06:03.000Z", :name=>"XYZ"}]
How about:
grouped = data.group_by do |item|
item[:id]
end
combined = grouped.map do |_id, hashes|
hashes.inject({}) do |memo, hash|
memo.merge(hash)
end
end
It works in two passes:
First group all hashes by the value of the :id key
This returns a Hash with the id as key, and an array (of all the hashes with this id) as value.
In a second pass all the hashes are merged and mapped to an array again.
arr = [
{ id: 77, phone: "9876543210", name: "Sure" },
{ id: 77, phone: "123456789", name: "Sure" },
{ id: 78, phone: "12345", name: "XYZ" }
]
You could use the form of Hash#update (aka merge!) that uses a block to compute the values of keys that are present in both hashes being merged.
arr.each_with_object({}) { |g,h| h.update(g[:id]=>g) { |_,o,n|
o.merge(phone: "#{o[:phone]}#{n[:phone]}") } }.values
#=> [{:id=>77, :phone=>"9876543210123456789", :name=>"Sure"},
# {:id=>78, :phone=>"12345", :name=>"XYZ"}]
Note that the receiver of Hash#values is the following.
#=> {77=>{:id=>77, :phone=>"9876543210123456789", :name=>"Sure"},
# 78=>{:id=>78, :phone=>"12345", :name=>"XYZ"}}
See the doc for Hash#update for definitions of the three block variables _, o and n. I used an underscore for the first variable (a valid name for a local variable) to signify that it is not used in the block calculation (a common practice).
Note that Hash#update can almost always be used when Enumerable#group_by can be used, and vice-versa.
Here's one way to use Hash#group_by here.
arr.group_by { |h| h[:id] }.
map { |_,a| a.first.merge(phone: a.map { |h| h[:phone] }.join) }
#=> [{:id=>77, :phone=>"9876543210123456789", :name=>"Sure"},
# {:id=>78, :phone=>"12345", :name=>"XYZ"}]
Note that
arr.group_by { |h| h[:id] }
#=> {77=>[{:id=>77, :phone=>"9876543210", :name=>"Sure"},
# {:id=>77, :phone=>"123456789", :name=>"Sure"}],
# 78=>[{:id=>78, :phone=>"12345", :name=>"XYZ"}]}

Resources