delete attributes nested within an array of objects Ruby on rails - ruby-on-rails

I need to remove the attribute nested1 that is inside the attr7_nested in a massive way, I don't need to persist this data in the bank, just store it in a variable to send to a log file.
example file follows:
[{
"attr1": 120,
"attr2": 24,
"attr3": 11400,
"attr4": "Caixa",
"attr5": 2000000,
"attr6": 1744000,
"attr7_nested": {
"nested1": 1,
"nested2": "Essential",
"nested3": "med",
"nested4": "Med"
}
},
{
"attr1": 120,
"attr2": 24,
"attr3": 11400,
"attr4": "Caixa",
"attr5": 2000000,
"attr6": 1744000,
"attr7_nested": {
"nested1": 1,
"nested2": "Ess",
"nested3": "med",
"nested4": "Med"
}
}]

When array is the array containing the nested hashes from your question than the following would remove the nested1 keys from all nested attr7_nested hashes:
array.each { |hash| hash[:attr7_nested].delete(:nested1) }

Related

How to merge two objects and keep count

I am building a Rails 5.2 app.
In this app I am working with statistics.
I generate two objects:
{
"total_project": {
"website": 1,
"google": 1,
"instagram": 1
}
}
And this:
{
"total_leads": {
"website": 1,
"google": 2,
"client_referral": 1
}
}
I need to merge these two objects into one single objects that increases the count. The desired result is:
{
"total_both": {
"website": 2,
"google": 3,
"instagram": 1,
"client_referral": 1
}
}
I tried this and it technically works, it merges the objects but the count is not updated:
#total_project = array_projects.group_by { |d| d[:entity_type] }.transform_values(&:count).symbolize_keys
#total_leads = array_leads.group_by { |d| d[:entity_type] }.transform_values(&:count).symbolize_keys
#total_sources = merged.merge **#total_project, **#total_leads
Please note that the attributes (sources) are dynamic from the database so I cannot hard code anything. The user can add their own sources.
#total_sources = #total_project.merge(#total_leads) do |key, ts_value, tp_value|
ts_value + tp_value
end
If there can be more than 2 sources, put everything in an array and do.
#total_sources = source_array.reduce do |accumulator, next_source|
accumulator.merge(next_source) { |key, v1, v2| v1 + v2 }
end
You may compute the desired result as follows.
arr = [{ "total_project": { "website": 1, "google": 1, "instagram": 1 } },
{ "total_leads": { "website": 1, "google": 2, "client_referral": 1 } }]
{ "total_both" => arr.flat_map(&:values)
.reduce { |h,g| h.merge(g) { |_,o,n| o+n } } }
#=> {"total_both"=>{:website=>2, :google=>3, :instagram=>1, :client_referral=>1}}
Note that
arr.flat_map(&:values)
#=> [{:website=>1, :google=>1, :instagram=>1},
# {:website=>1, :google=>2, :client_referral=>1}]
Had I used Array#map this would have been
arr.map(&:values)
#=> [[{:website=>1, :google=>1, :instagram=>1}],
# [{:website=>1, :google=>2, :client_referral=>1}]]
See Enumerable#flat_map, Enumerable#reduce and the form of Hash#merge that takes a block (here { |_,o,n| o+n }) which returns the values of keys that are present in both hashes being merged. See the doc for merge for definitions of the three block variables (here _, o and n). I have named the first block variable (holding the common key) _ to signal to the reader that it is not used in the block calculation (a common Ruby convention).

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

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

Why does Rails `includes` method not add included model to attributes?

I'm running into a strange issue with nested rails models related to the includes method. I'm attempting to simply move an item from one object to its parent like so:
Current:
[
{
"created_on": "2014-09-11T15:52:34-04:00",
"id": 8,
"mail_notification": false,
"project_id": 2,
"user_id": 15,
"member_roles": [
{
"id": 10,
"inherited_from": null,
"member_id": 8,
"role_id": 3
}
]
}
]
Needed:
[
{
"created_on": "2014-09-11T15:52:34-04:00",
"id": 8,
"mail_notification": false,
"project_id": 2,
"user_id": 15,
"role_id": 3
}
]
For some reason, when I loop through the current object, It strips out the :member_roles. Case in point:
members = Member.includes(:member_roles).find_all_by_project_id(#project)
# Contains :member_roles
puts members.to_json(include: [:member_roles])
#=> [{"created_on":"2014-09-11T15:52:34-04:00","id":8,"mail_notification":false,"project_id":2,"user_id":15,"member_roles":[{"id":10,"inherited_from":null,"member_id":8,"role_id":3}]}]
# Does not contain :member_roles
puts members.first.attributes
#=> {"id"=>8, "user_id"=>15, "project_id"=>2, "created_on"=>Thu, 11 Sep 2014 15:52:34 EDT -04:00, "mail_notification"=>false}
Why does the :member_roles object disappear?
you cannot do what you expect.
Member.includes(:member_roles) is eager loading your relation (ie it fetchs all the member_roles instances required by the member collection when you actually use on of this object the first time)
to_json(include: [:member_roles]) is including the json reprensation of the related model in the parent member model json.
What you describe is called method delegation (Module.delegate), but since you have a one to many relation between your 2 models you cannot do it

rails extract data from simple json response

I need to extract some data from a JSON response i'm serving up from curb.
Previously I wasn't calling symbolize_keys, but i thought that would make my attempt work.
The controller action:
http = Curl.get("http://api.foobar.com/thing/thing_name/catalog_items.json?per_page=1&page=1") do|http|
http.headers['X-Api-Key'] = 'georgeBushSucks'
end
pre_keys = http.body_str
#foobar = ActiveSupport::JSON.decode(pre_keys).symbolize_keys
In the view (getting undefined method `current_price' )
#foobar.current_price
I also tried #foobar.data[0]['current_price'] with the same result
JSON response from action:
{
"data": {
"catalog_items": [
{
"current_price": "9999.0",
"close_date": "2013-05-14T16:08:00-04:00",
"open_date": "2013-04-24T11:00:00-04:00",
"stuff_count": 82,
"minimum_price": "590000.0",
"id": 337478,
"estimated_price": "50000.0",
"name": "This is a really cool name",
"current_winner_id": 696969,
"images": [
{
"thumb_url": "http://foobar.com/images/93695/thumb.png?1365714300",
"detail_url": "http://foobar.com/images/93695/detail.png?1365714300",
"position": 1
},
{
"thumb_url": "http://foobar.com/images/95090/thumb.jpg?1366813823",
"detail_url": "http://foobar.com/images/95090/detail.jpg?1366813823",
"position": 2
}
]
}
]
},
"pagination": {
"per_page": 1,
"page": 1,
"total_pages": 131,
"total_objects": 131
}
}
Please note that accessing hash's element in Rails work in models. To use it on hash, you have to use OpenStruct object. It's part of standard library in rails.
Considering, #foobar has decoded JSON as you have.
obj = OpenStruct.new(#foobar)
obj.data
#=> Hash
But, note that, obj.data.catalog_items willn't work, because that is an hash, and again not an OpenStruct object. To aid this, we have recursive-open-struct, which will do the job for you.
Alternative solution [1]:
#foobar[:data]['catalog_items'].first['current_price']
But, ugly.
Alternative solution [2]:
Open Hash class, use method_missing ability as :
class Hash
def method_missing(key)
self[key.to_s]
end
end
Hope it helps. :)

Resources