Merging dynamically generated attributes into a new entry and summing their values - ruby-on-rails

I'm looking for some advice on how to properly merge some key/value pairs into a separate database entry and summing their values.
I have a Task which has a Vendor_Upload which has many Vendor_Shipping_Logs which has many Vendor_Shipping_Log_Products. I'm not sure if the deep nesting makes a difference, but the important values to look at here are the Item_ID and Quantity.
This is currently how the parameters are spit out:
Parameters: {
"task"=>{
"task_type"=>"Vendor Upload",
"vendor_upload_attributes"=>{
"upload_type"=>"Warranty Orders",
"vendor_shipping_logs_attributes"=>{
"1490674883303"=>{
"guest_name"=>"Martin Crane",
"order_number"=>"33101",
"vendor_shipping_log_products_attributes"=>{
"1490675774108"=>{
"item_id"=>"211",
"quantity"=>"3"
},
"1490675775147"=>{
"item_id"=>"213",
"quantity"=>"6"
}
}
},
"1490674884454"=>{
"guest_name"=>"Frasier Crane",
"order_number"=>"33102",
"vendor_shipping_log_products_attributes"=>{
"1490675808026"=>{
"item_id"=>"214",
"quantity"=>"10"
},
"1490675808744"=>{
"item_id"=>"213",
"quantity"=>"1"
}
}
},
"1490674885293"=>{
"guest_name"=>"Niles Crane",
"order_number"=>"33103",
"vendor_shipping_log_products_attributes"=>{
"1490675837184"=>{
"item_id"=>"211",
"quantity"=>"3"
}
}
},
"1490674886373"=>{
"guest_name"=>"Daphne Moon",
"order_number"=>"33104",
"vendor_shipping_log_products_attributes"=>{
"1490675852950"=>{
"item_id"=>"213",
"quantity"=>"8"
},
"1490675853845"=>{
"item_id"=>"214",
"quantity"=>"11"
}
}
}
}
}
}
}
Upon submission I want to merge each unique Vendor_Shipping_Log_Products Item_IDs and sum their quantities into a new Stockmovement_Batch as a nested Stockmovement to keep my inventories up to date.
See example patameters here of what I would like the output to look like:
Parameters: {
"stockmovement_batch"=>{
"stockmovement_type"=>"Ecomm Order",
"stockmovements_attributes"=>{
"1490676054881"=>{
"item_id"=>"211",
"adjust_quantity"=>"-6"
},
"1490676055897"=>{
"item_id"=>"213",
"adjust_quantity"=>"-15"
},
"1490676057616"=>{
"item_id"=>"214",
"adjust_quantity"=>"-21"
}
}
}
}
Is this something I can do all in one simple go, or do I have to stick with doing each process in a separate form?

First you need to separate out the values you want to iterate through:
data = params.require("task")
.require("vendor_upload_attributes")
.require("vendor_shipping_logs_attributes")
Then pull the vendor_shipping_log_products_attributes and flatten it to an array of hashes:
logs = data.values.map do |h|
h["vendor_shipping_log_products_attributes"].values
end.flatten
# => [{"item_id"=>"211", "quantity"=>"3"}, {"item_id"=>"213", "quantity"=>"6"}, {"item_id"=>"214", "quantity"=>"10"}, {"item_id"=>"213", "quantity"=>"1"}, {"item_id"=>"211", "quantity"=>"3"}, {"item_id"=>"213", "quantity"=>"8"}, {"item_id"=>"214", "quantity"=>"11"}]
Then we merge the data by creating a intermediary hash where we use the item_id as keys.
stockmovements = logs.each_with_object({}) do |hash, memo|
id = hash["item_id"]
memo[id] ||= []
memo[id].push(hash["quantity"].to_i)
end
# => {"211"=>[3, 3], "213"=>[6, 1, 8], "214"=>[10, 11]}
We then can then map the result and sum the values:
stockmovements.map do |(k,v)|
{
item_id: k,
adjust_quantity: 0 - v.sum
}
end
# => [{:item_id=>"211", :adjust_quantity=>-6}, {:item_id=>"213", :adjust_quantity=>-15}, {:item_id=>"214", :adjust_quantity=>-21}]

Related

Is it possible to select certain elements without iterating through the array?

I have an array of objects, each of which has the property :cow either set to false or true:
animals = [
{
id: 1,
cow: true
},
{
id: 2,
cow: true
},
{
id: 3,
cow: true
},
{
id: 4,
cow: false
},
{
id: 5,
cow: false
}
]
I need to select all members of the array that pass a condition without iterating through every element of the array.
Is it possible?
I tried:
notCows = animals.reject { |a| !a[:cow] }
notCows = animals[0, 1, 2]
which doesn't work.
No, this is impossible. In order to find all elements that satisfy a certain condition, you need to look at all elements to see whether they satisfy that condition. It is simply logically not possible to find all elements of a collection without iterating through all elements of the collection.
You were almost there, use Enumerable#select (which scans the all the member of the collection, by the way):
animals.select { |animal| animal[:cow] }
#=> [{:id=>1, :cow=>true}, {:id=>2, :cow=>true}, {:id=>3, :cow=>true}]
Or the opposite:
animals.select { |animal| !animal[:cow] }
#=> [{:id=>4, :cow=>false}, {:id=>5, :cow=>false}]
The returned results are still Ruby objects: Arrays of Hashes.
As alternative you can group by status (Enumerable#group_by):
animals.group_by { |a| a[:cow] }
#=> {true=>[{:id=>1, :cow=>true}, {:id=>2, :cow=>true}, {:id=>3, :cow=>true}], false=>[{:id=>4, :cow=>false}, {:id=>5, :cow=>false}]}

Rail convert array into group

I am trying to convert one of my array into some format where it can convert itself into table format.
I have an array which is:
[
{
id: 1,
Revenue_Account: "Revenue Receipt",
Amount: 59567,
Year: "2012-13",
created_at: "2018-08-21T06:30:17.000Z",
updated_at: "2018-08-21T06:30:17.000Z"
},
{
id: 2,
Revenue_Account: "Revenue Expenditure ",
Amount: 54466,
Year: "2012-13",
created_at: "2018-08-21T06:30:17.000Z",
updated_at: "2018-08-21T06:30:17.000Z"
},
...
]
Full code of my array link to my actual array
I want this data to be converted into this format:
data: [
{
id: 1,
Sector: "Revenue Receipt",
2012-13: 59567,
2013-14: 68919,
2014-15: 72570,
2015-16: 96123,
2016-17: 105585,
2017-18_BE: 137158,
},
{
id: 2,
Sector: "Revenue Expenditure",
2012-13: 59567,
2013-14: 68919,
2014-15: 72570,
2015-16: 96123,
2016-17: 105585,
2017-18_BE: 137158,
},
....
]
I am using this code to group my array:
group = b.group_by{|data| data[:Revenue_Account]}
this is grouping my data as I am expecting in order to achieve my goal I am trying this code.
group = b.group_by{|data| data[:Revenue_Account]}
du = []
group.each do |i|
du.push({Sector:i[0]})
end
This is giving me Sector wise result how can I add year in my code.
You can't have a single id in there because you're grouping up many entries with different ids, but this is how you'd get the array in the format you're asking for:
grouped = {}
b.each do |x|
grouped[x[:Revenue_Account]] ||= {}
grouped[x[:Revenue_Account]][:Sector] = x[:Revenue_Account]
grouped[x[:Revenue_Account]][x[:Year]] = x[:Amount]
end
return {data: grouped.values}
Which gets you:
{
:data=>[
{
:Sector=>"Revenue Receipt",
"2012-13"=>59567,
"2013-14"=>68919,
"2014-15"=>78417,
"2015-16"=>96123,
"2016-17"=>105585,
"2017-18_BE"=>137158
},
{
:Sector=>"Revenue Expenditure ",
"2012-13"=>54466,
"2013-14"=>62477,
"2014-15"=>72570,
"2015-16"=>83616,
"2016-17"=>94765,
"2017-18_BE"=>122603
},
]
}
We build a new hash by looping through the original hash and creating hash keys if they don't exist. Then we start assigning values as you want them to be in the output. On each iteration, we're creating a new key in this hash for the Revenue_Account value if its the first time we've seen it. Then we assign that particular Revenue_Account's Date and Amount to the output. So for value 'Revenue Receipt' it looks like this:
Grouped hash starts off as empty
On first iteration, we see that group["Revenue Receipt"] is nil, so we initialize it with an empty hash via ||= (assign if nil)
We then assign :Sector => "Revenue Receipt" and this entry's Year and Amount, "2012-13" => 59567
Our grouped hash looks like: {"Revenue Receipt" => {:Sector => "Revenue Receipt", "2012-13" => 59567}
On the next iteration we see that group["Revenue Receipt"] is not nil, so ||= does not override it with an empty hash
We then assign :Sector => "Revenue Receipt" and this entry's Year and Amount, "2012-14" => 68919, which adds a new key/value to the existing hash
Our grouped hash now looks like: {"Revenue Receipt" => {:Sector => "Revenue Receipt", "2012-13" => 59567, "2012-14" => 68919}
After we parse the entire array, we now have a hash that has a key of the Revenue_Account, and values which look like the hash output you're expecting.
We discard the key and return only the hash values, which gets you the final output.
Another option, directly manipulating the array.
array_of_data = array
.each { |h| h[:Sector] = h.delete(:Revenue_Account) }
.each { |h| h[h[:Year]] = h[:Amount]}
.each { |h| h.delete_if{ |k, _| k == :created_at || k == :updated_at || k == :id || k == :Year || k == :Amount} }
.group_by { |h| h[:Sector] }
.values.map { |a| a.inject(:merge) }
Then just:
h = {}
h[:data] = array_of_data
To understand what happens along the code, just ad line by line outputting the result, like:
p array
.each { |h| h[:Sector] = h.delete(:Revenue_Account) }
Then:
p array
.each { |h| h[:Sector] = h.delete(:Revenue_Account) }
.each { |h| h[h[:Year]] = h[:Amount]}
Etcetera...
To understand .inject(:merge), see here Rails mapping array of hashes onto single hash

Loop specific values from deep nested hash and array

I'm new to ruby and am having difficulty with looping through deep nested hashes and arrays.
Say I have the following JSON:
{
"Resume":{
.... data ....
},
"StructuredXMLResume":{
"ContactInfo":{
.... data ....
}
]
},
"EmploymentHistory":{
"EmployerOrg":[
{
"EmployerOrgName":"ABC Corp.",
"PositionHistory":[
{
.... data ....
]
},
{
"EmployerOrgName":"National Geo.",
"PositionHistory":[
{
.... data ....
}
]
}
]
}
]
}
.
resume.["Resume"]["StructuredXMLResume"]["EmploymentHistory"]["EmployerOrg"][0]["EmployerOrgName"]
gives me ABC Corp. and
resume.["Resume"]["StructuredXMLResume"]["EmploymentHistory"]["EmployerOrg"][1]["EmployerOrgName"]
gives me National Geo.
How do I loop to print each EmployerOrgName?
Use:
resume.["Resume"]["StructuredXMLResume"]["EmploymentHistory"]["EmployerOrg"].each do |employer_org|
puts employer_org["EmployerOrgName"] # or whatever you want to do with the employer_org hash
end
You can use a enumerator, like each, on any "thing" that either is or behaves like an array/hash object, so in this case you would want to:
all_employerorgs = resume.["Resume"]["StructuredXMLResume"]["EmploymentHistory"]["EmployerOrg"]
# which is an array of hashes that then you can iterate through
# you could iterate directly on that but for readibility
# I would always assign it to a var
all_employerorgs.each do |employerorg|
puts employerorg['EmployerOrgName']
end

ActiveRecord query from an array of hash

I have an array of hash.
eg) array_of_hash = [ { id: 20, name: 'John' }, { id: 30, name: 'Doe'} ]
I would like to get records which match all the criteria in a particular hash.
So the query I want to get executed is
SELECT persons.* FROM persons WHERE persons.id = 20 AND persons.name = 'John' OR persons.id = 30 AND persons.name = 'Doe'
What is the best way to construct this query from an array of hash?
I think this is okay:
ids = array_of_hash.map { |h| h[:id] }
names = array_of_hash.map { |h| h[:name] }
Person.where(id: ids, name: names)
(although it's not super generic)
other attempt:
people = Person.all
array_of_hash.each do |h|
people = people.where(h)
end
people # => will generate a long long query.
Try to map all conditions to your ActiveRecord model independently and flatten the result array afterwards:
array_of_hash.map{ |where_clause| Person.where(where_clause) }.flatten

ruby on rails looping through a list of key value elements

I have a list of key value pairs like this.
PERSON_SUMMARY = {
first_names: %w(Mike Tim Jim kevin Alan Sara John Sammy t'Renée),
last_names: %w(Robinson Jackson Fox Terry Ali Brits Tyson Willis-St.\ Paul),
offenses: [
{ offense_name:'Speeding',
penalties: [
{ penalty_name: 'Prison', severity: 'Medium' },
{ penalty_name: 'Ticket', severity: 'Low' }
]
},
{ offense_name:'Shoplifting',
penalties: [
{ penalty_name: 'Prison', severity: 'Medium' },
{ penalty_name: 'Fine', severity: 'Low' }
]
}
]
}
I want to store and print only offense_name,**penalty_name** and severity one by one , but I am not able to get the right syntax.
Here is what I have tried so far:
PERSON_SUMMARY[:offenses].each do |offense|
offense_name = offense[:offense_name]
offense[:penalties].each do |penalty|
penalty_name = penalty[:penalty_name]
severity_val = penalty[:severity]
end
end
EDIT: Eventually I need to insert it into the database table through this function:
PersonOffense.where(person_id: person.id).first_or_create(
name: offense_name,
penalty: penalty_name ,
severity: severity_val
end
But I notice an issue, there are multiple penalty names above. Not sure how to insert them.
For example,
I need to insert offense_name twice in my table so that there are 2 entries in the table.
Speeding Prison Medium
Speeding Ticket Low
EDIT: I like Jesse's answer below. How can I use it to insert it in the same order to my method above (inserting offense_name,penalty_name and severity with the result of the answer given below by Jesse.
Rather than just using each, if you map over the offenses, and then flatten them in the end, you'll get what you want:
offenses = PERSON_SUMMARY[:offenses].map do |offense|
offense[:penalties].map do |penalty|
penalty.merge(name: offense[:offense_name])
end
end.flatten
=> [{:penalty_name=>"Prison", :severity=>"Medium", :name=>"Speeding"}, {:penalty_name=>"Ticket", :severity=>"Low", :name=>"Speeding"}, {:penalty_name=>"Prison", :severity=>"Medium", :name=>"Shoplifting"}, {:penalty_name=>"Fine", :severity=>"Low", :name=>"Shoplifting"}]
UPDATE
In ruby, the following is a hash, and you already have a hash
So you can just loop through your new array and create your PersonOffense:
offenses.each do |hash|
PersonOffense.where(person_id: person.id).first_or_create( hash )
end

Resources