Loop specific values from deep nested hash and array - ruby-on-rails

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

Related

How to iterate over an array of arrays in Ruby

I have an array of arrays that contains details of three tests. This is the array:
test_arr = [
[‘unit test 1’, ‘physics’, ‘25’],
[‘unit test 2’, ‘chemistry’, ‘30’],
[‘final test’, ‘math’, ‘50’]
]
I want to iterate over this array three times (because there are only three tests whose data is contained in the array) to get a hash in an array containing only the subject and marks.
I do the iteration as follows:
test_arr.each do |ta|
all_test_details << { subject: ta[1], marks: ta[2] }
end
I want all_test_details to read like this =>
[ { subject: ‘physics’, marks: 25 },
{ subject: ‘chemistry’, marks: 30 },
{ subject: ‘math’, marks: 50 } ]
But when I run the code, what I get is all_test_details repeated ten times. I’m unable to figure out why.
How do I get the names and marks of the test in a single array containing a hash?
Make use of map:
test_arr.map { |data| { subject: data[1], marks: data[2] } }
#=> [{:subject=>"physics", :marks=>"25"},
#=> {:subject=>"chemistry", :marks=>"30"},
#=> {:subject=>"math", :marks=>"50"}]

Merging dynamically generated attributes into a new entry and summing their values

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}]

Rails: How to update the hash in an array

How to update a hash from an array?
Sample Data
form_fields = [
{
key: '1',
properties: null
},
{
key: '2',
properties: {"options"=>[{"label"=>"Europe", "value"=>"europe"}, {"label"=>"North America", "type"=>"groupstart"}, {"label"=>"Canada", "value"=>"canada"}, {"label"=>"USA", "value"=>"usa"}, {"label"=>"", "type"=>"groupend"}]}
}
]
Code I have so far
form_fields = form_fields.map {
|field| {
field.properties = field.properties ? JSON.parse(field.properties) : {}
}
I built this based on some other questions I came across such as this one How do I modify an array while I am iterating over it in Ruby?
The syntax for the map is very close to what you already have, the correct syntax would be like this:
form_fields = form_fields.map {
|field|
...
}
To access a object in the Hash structure you would use the symbol, or string as the selector, like this:
field[:properties]
field[:properties]["options"]
The code you have in your map, does not really make sense. The object stored in the code you provided is already ruby code, the hash keys is just strings instead of symbols.
You do also not want to update your form_fields variable to the result of your map, since that will overwrite your array, and only keep the last element in the array.
I believe what you want is something like this:
form_fields.map { |field|
field[:properties] = field[:properties] ? field[:properties] : {}
}
That will turn your array from:
[{:key=>"1", :properties=>nil}, {:key=>"2", :properties=>{ ... }}]
to:
[{:key=>"1", :properties=>{}}, {:key=>"2", :properties=>{ ... }}]

Rails 4 and returning JSON response: how to correctly append extra data?

I have a SQL query returns some data, here is some sample output:
[
{
"AccountCode": "111123456",
"AccountID": 123456,
"BalanceCurrent": "-8.0",
"Phone": "123456888",
}
]
This is a Hash with an array. There are times when there will be multiple hashes within the array. Just one in this example though.
As stated, this data comes directly from the database.
I have a lookup_phone method in my Customer model that runs the SQL query and then executed in the customer_controller.rb file like so:
customer_phone = Customer.lookup_phone(params[:Phone])
Now, I need to append some extra data to these hash(es) that do not come from the database, like so:
data = [
:match_found => true,
:transfer_flag => false,
:confirm_id => 2
]
This data variable needs to be WITHIN each hash object, not a separate hash object on its own.
Using a simple array concat or + always makes the data a separate hash object. I've come across some good posts saying to use reduce along with merge, but those are Hash methods, not Array methods.
If I try to set data as a Hash instead of an array, I get
no implicit conversion of Hash into Array when I try to do
customer_phone.reduce({}, :merge)
after running customer_phone += data
What is the proper way to append data to an existing Hash object?
maybe combine each and merge
base = [
{
"AccountCode": "111123456",
"AccountID": 123456,
"BalanceCurrent": "-8.0",
"Phone": "123456888",
}
]
data = {:match_found=>true, :transfer_flag=>false, :confirm_id=>2}
base.each { |el| el.merge!(data) }
#=> [{:AccountCode=>"111123456", :AccountID=>123456, :BalanceCurrent=>"-8.0", :Phone=>"123456888", :match_found=>true, :transfer_flag=>false, :confirm_id=>2}]
You can add attr_accessor to your Customer model like this
class Customer
attr_accessor :data
end
With your data array:
data_array = [
:match_found => true,
:transfer_flag => false,
:confirm_id => 2
]
Then, you can execute the query combined with each function:
customer_phone = Customer.lookup_phone(params[:Phone]).each {|e| e.data = data_array}
Access it:
customer_phone.first.data
To render json:
render json: customer_phone, methods: [:data]

iterate through array, adjust a virtual attribute and return array with adjusted value

I have a rails class called a tag:
class Tag < ActiveRecord::Base
attr_accessor :is_enabled_on_item # a virtual attribute for ouptutting json
and I want to adjust the is_enabled_on_item like the following:
t.each { |tmp| tmp.is_enabled_on_item=true if current_tag_ids.include?(tmp.tagid) }
to get like:
[
{
tagid:1,
phrase: "blue",
is_enabled_on_item: true
},
{
tagid:2,
phrase: "yellow",
is_enabled_on_item: false
}
]
I tried collect
t.collect { |tmp| tmp.is_enabled_on_item=true if current_tag_ids.include?(tmp.tagid) }
but itreturns [true, false]. How would I achieve what I want?
edit #1
I'd want it to return an array of tags.
Something like this would work but the intermediary arr seems unnecessary:
arr=[]
t.each do |tmp|
tmp.is_enabled_on_item=current_tag_ids.include?(tmp.tagid)
arr << tmp
end
arr
t.map do |tmp|
tmp.attributes.merge(is_enabled_on_item: current_tag_ids.include?(tmp.tagid))
end
Your take on collect is almost right, try this:
t.collect { |tmp| tmp.is_enabled_on_item=current_tag_ids.include?(tmp.tagid); tmp }
Note that assignment returns assigned value, while you have to return tags in block in order to collect tags.
It would be nice to move one step forward and use more canonical method and argument names:
tags.map { |tag| tag.is_enabled_on_item=current_tag_ids.include?(tag.tagid); tag }

Resources