Rails how to group by nested hash - ruby-on-rails

I have no clue to solve this question. I got a nested hash inside an array called data. Here is its structure.
data =
[
{
:id => 1,
:name => "S1",
:children => [
{
:id => 10,
:name => "S10",
:children => [
{
:id => 20,
:name => "S20"
}
]
}
]
},
{
:id => 1,
:name => "S1",
:children => [
{
:id => 10,
:name => "S10",
:children => [
{
:id => 21,
:name => "S21"
}
]
}
]
},
{
:id => 1,
:name => "S1",
:children => [
{
:id => 11,
:name => "S11",
:children => [
{
:id => 22,
:name => "S22"
}
]
}
]
}
]
As you can see, there are bunch of elements which have the same id in the first layer or second layer, so I need to group them.
I hope the result will be
result=
[
{
:id => 1,
:name => "S1",
:children => [
{
:id => 10,
:name => "S10",
:children => [
{
:id => 20,
:name => "S20"
},
{
:id => 21,
:name => "S21"
}
]
},
{
:id => 11,
:name => "S11",
:children => [
{
:id => 22,
:name => "S22"
}
]
}
]
}
]
I've tried somthing like
data.group_by{|s| s[:id]}
However, it would only group the first layer, I don't know how to group nested structure.

Yeah you need some kind of recursive method to recursively combine and group the nested children.
This produces the result you want:
def group(data)
r = {}
# Combine the children
data.each do |d|
if r[d[:id]]
r[d[:id]][:children] += d[:children]
else
r[d[:id]] = d
end
end
# Now group the children
r.values.map do |d|
d[:children] = group(d[:children]) if d[:children]
d
end
end

Related

ARRAY ACCESS AND INSERTION RECORD DATABASE

I would need to access the array to retrieve the information and insert it into the database (Code, Customer, Phone1, Phone2). Someone can help me?
{
:recordset => {
:row => [
[0] {
:property => [
[0] {
:name => "Code",
:value => "C0001"
},
[1] {
:name => "Customer",
:value => "ROSSI MARIO"
},
[2] {
:name => "Phone1",
:value => "1234567890"
}
]
},
[1] {
:property => [
[0] {
:name => "Code",
:value => "C0002"
},
[1] {
:name => "Customer",
:value => "VERDE VINCENT"
},
[2] {
:name => "Phone1",
:value => "9876543210"
},
[3] {
:name => "Phone2",
:value => "2468101214"
}
]
}
]
},
:#xmlns => "http://localhost/test"
}
p.s. The Phone2 value during the SOAP call is only displayed if it is present in an archive
Thank you
Your data doesn't look like a valid ruby structure. Once you are able to convert it to be more ruby-like, you can make use of each_with_object to generate a well formatted set of attributes and their values from your data:
data
=> {:recordset=>{
:row=>[{
:property=>[
{:name=>"Code", :value=>"C0001"},
{:name=>"Customer", :value=>"ROSSI MARIO"},
{:name=>"Phone1", :value=>"1234567890"}
]
}, {
:property=>[
{:name=>"Code", :value=>"C0002"},
{:name=>"Customer", :value=>"VERDE VINCENT"},
{:name=>"Phone1", :value=>"9876543210"},
{:name=>"Phone2", :value=>"2468101214"}
]
}]
},
:#xmlns=>"http://localhost/test"}
data.keys
=> [:recordset, :#xmlns]
data[:recordset][:row].count
=> 2 # There are 2 set of attribute-value pairs
result = data[:recordset][:row].each_with_object([]) do |hash, out|
out << hash[:property].each_with_object({}) do |h, o|
o[h[:name]] = h[:value]
end
end
=> [{"Code"=>"C0001", "Customer"=>"ROSSI MARIO", "Phone1"=>"1234567890"},
{"Code"=>"C0002", "Customer"=>"VERDE VINCENT", "Phone1"=>"9876543210", "Phone2"=>"2468101214"}]
Now, you can iterate over result and create respective records in database.

Rails: Values not getting assigned to array when in a do loop

I have an array called components
components = [
{
:name => "Component 1",
:order => "1"
},
{
:name => "Component 2",
:order => "2"
},
{
:name => "Component 3",
:order => "3"
},
{
:name => "Component 4",
:order => "4"
}
]
And another array called subcomponent
subcomponents = [
{
:name => "Subcomponent 1A",
:order => "1A"
},
{
:name => "Subomponent 1B",
:order => "1B"
},
{
:name => "Component 2A",
:order => "2A"
},
{
:name => "Component 4A",
:order => "4A"
}
]
I'm trying to get it so that the subcomponents appear underneath a component object if they are a subcomponent of that object. This is the expected output:
components = [
{
:name => "Component 1",
:order => "1",
:subcomponents => [
{
:name => "Subcomponent 1A",
:order => "1A"
},
{
:name => "Subomponent 1B",
:order => "1B"
}
]
},
{
:name => "Component 2",
:order => "2",
:subcomponents => [
{
:name => "Component 2A",
:order => "2A"
}
]
},
{
:name => "Component 3",
:order => "3",
:subcomponent => []
},
{
:name => "Component 4",
:order => "4",
:subcomponent => [
{
:name => "Component 4A",
:order => "4A"
}
]
}
]
I've created a loop to try to do this:
components.each do |c|
c.class.module_eval { attr_accessor :subcomponents}
c.subcomponents = []
subcomponents.each do |s|
if /#{c["order"]}[A-Z]/ =~ s["order"]
#This is never assigned but it does make it into this statement
c.subcomponents << s
end
end
puts c.subcomponents.to_s # This prints []
end
As I've put in my code comments, the subcomponent is never assigned back to the component.subcomponents arrays even though the if statement is stepped into.
Am I missing something where the scope of component.subcomponents isn't accessible anymore or is at a different scope?
What should I be doing to ensure I can assign value component.subcomponents?
components.each do |c|
c[:subcomponents] = []
subcomponents.each do |s|
c[:subcomponents] << s if /#{c[:order]}[A-Z]/ =~ s[:order]
end
end
Hope this work.
components.each do |component|
component[:subcomponents] = []
end
subcomponents.each do |subcomponent|
order = subcomponent[:order][/\d+/]
components.each do |component|
if order == component[:order]
component[:subcomponents] << subcomponent
end
end
end
You can do it in O(n) using this:
subcomponent_hash = Hash.new { |h, k| h[k] = [] }
subcomponents.each do |sc|
order = sc[:order][/\d+/]
subcomponent_hash[order] << sc
end
components.each do |c|
c[:subcomponents] = subcomponent_hash[c[:order]]
end

Include counts in as_json method

I have a Customers table and an Orders table. Each customer has multiple orders. When I do a Customers.all.as_json(:include => :orders), I get all the orders but I just want to get the count
I tried Customers.all.as_json(:include => {:orders => {:only => [], :methods => [:custom_method_to_get_order_count}}) though it gets the correct count I have array of object that are equal to count.
Wrong output! :(
[
{
:customer_id => 1,
:customer_name => "ABC",
:orders => [
{:custom_method_to_get_order_count => 2},
{:custom_method_to_get_order_count => 2}
]
},
{
:customer_id => 2,
:customer_name => "DEF",
:orders => [
{:custom_method_to_get_order_count => 3},
{:custom_method_to_get_order_count => 3},
{:custom_method_to_get_order_count => 3}
]
},
{
:customer_id => 3,
:customer_name => "XYZ",
:orders => [
{:custom_method_to_get_order_count => 10},
{:custom_method_to_get_order_count => 10},
{:custom_method_to_get_order_count => 10},
{:custom_method_to_get_order_count => 10},
{:custom_method_to_get_order_count => 10},
{:custom_method_to_get_order_count => 10},
{:custom_method_to_get_order_count => 10},
{:custom_method_to_get_order_count => 10},
{:custom_method_to_get_order_count => 10},
{:custom_method_to_get_order_count => 10}
]
}
]
Correct output I am looking for
[
{
:customer_id => 1,
:customer_name => "ABC",
:orders => {
:custom_method_to_get_order_count => 2
}
},
{
:customer_id => 2,
:customer_name => "DEF",
:orders => {
:custom_method_to_get_order_count => 3
}
},
{
:customer_id => 3,
:customer_name => "XYZ",
:orders => {
:custom_method_to_get_order_count => 10
}
}
]
You can also use like this:
class Customers
def get_orders_count
self.orders.count
end
end
and now you can get all orders count in customer object:
Customers.all.as_json(method: :get_orders_count)
Output will be something like:
[
{
:customer_id => 1,
:customer_name => "ABC",
:get_orders_count => 12
},
{
:customer_id => 2,
:customer_name => "DEF",
:get_orders_count => 20
},
{
:customer_id => 3,
:customer_name => "XYZ",
:get_orders_count => 50
}
]
Never mind! I got the answer.
class Customers
def custom_method_to_get_order_count
Orders.where(:customer_id => id).count
end
end
What's happening to me, whenever I post the question on SO, I get the answer!

What is causing Aws::CloudFront::Errors::MalformedInput: Unexpected list element termination

I'm attempting to create a new AWS Cloudfront Distribution with v2 of the ruby AWS SDK and cannot figure out what is causing this error.
Aws::CloudFront::Errors::MalformedInput: Unexpected list element termination
client = Aws::CloudFront::Client.new
resp = client.create_distribution({
distribution_config: {
caller_reference: Time.now.to_i.to_s,
:aliases => {
:quantity => 1,
:items => [Name.generate_name]
},
:origins => {
:quantity => 1,
:items => [
{
:id => "#{self.id}-distribution",
:domain_name => "example-static.s3-website-us-east-1.amazonaws.com",
:origin_path => "/#{self.id}",
:custom_headers => {
:quantity => 0,
:items => []
},
:custom_origin_config => {
:http_port => 80,
:https_port => 443,
:origin_protocol_policy => "http-only",
:origin_ssl_protocols => {
:quantity => 3,
:items => ["TLSv1","TLSv1.1","TLSv1.2"]
}
}
}
]
},
:default_cache_behavior => {
:target_origin_id => "Custom-example-static.s3-website-us-east-1.amazonaws.com/#{self.id}",
:forwarded_values => {
:query_string => true,
:cookies => {
:forward => "none"
},
:headers => {
:quantity => 1,
:items => ["Origin"]
}
},
:trusted_signers => {
:enabled => false,
:quantity => 0
},
:viewer_protocol_policy => "allow-all",
:min_ttl => 0,
:allowed_methods => {
:quantity => 3,
:items => ["HEAD","GET","OPTIONS"],
:cached_methods => {
:quantity => 3,
:items => ["HEAD","GET","OPTIONS"]
}
},
:smooth_streaming => false,
:default_ttl => 86400,
:max_ttl => 31536000,
:compress => true
},
:cache_behaviors => {
:quantity => 0
},
:custom_error_responses => {
:quantity => 0
},
:comment => "",
logging: {
enabled: true, # required
include_cookies: false, # required
bucket: "example-logs", # required
prefix: "#{self.id}", # required
},
:price_class => "PriceClass_100",
:enabled => true,
:restrictions => {
:geo_restriction => {
:restriction_type => "none",
:quantity => 0
}
}
}
})
I compared the results I got back from an existing instance with
client = Aws::CloudFront::Client.new(:http_wire_trace => true)
resp = client.get_distribution_config({
:id => '<ID>'
})
Changing the payload from
:custom_headers => {
:quantity => 0,
:items => []
},
to
:custom_headers => {
:quantity => 0
},
seemed to fix the same error message for me.

Advanced grouping in Ruby

I want to group events by their days.
Prefact:
Available days are 1,2,3,4,5,6,7
One event can not contain duplicates. e.g [1,1,2,3]
Setup is Ruby 1.9.2 with Rails 3.2
An event is containing like 8-10 more attributes(unnecessary to include in example, but should be considered, these attributes should still be there after grouping.) In other words the event objects should not be altered only grouped as is.
Consider an array with objects:
events = [
{
:name => "event1",
:days => [1,2,3,4,5]
},
{
:name => "event2",
:days => [1,4,5]
},
{
:name => "event3",
:days => [1]
},
{
:name => "event4",
:days => [2]
},
{
:name => "event5",
:days => [3]
},
{
:name => "event6",
:days => [4]
},
{
:name => "event7",
:days => [5]
},
{
:name => "event8",
:days => [1,2,3]
},
{
:name => "event9",
:days => [1,5]
},
{
:name => "event10",
:days => [1,2]
},
{
:name => "event11",
:days => [1,2,3,4,5]
}
]
To be grouped an event must have at least 3 days. And these days should be in numerically order.
Example(should be grouped): [1,2,3]
Example(should not be grouped): [1,4,5]
Events that's not suitable for grouping should be placed in each day they contain.
Example: [1,4,5] should be placed in 1,4 and 5.
The desired result of events array above:
[
{
:heading => "1",
:events => [
{
:name => "event3",
:days => [1]
},
{
:name => "event9",
:days => [1,5]
},
{
:name => "event10",
:days => [1,2]
},
{
:name => "event2",
:days => [1,4,5]
}
]
},
{
:heading => "2",
:events => [
{
:name => "event4",
:days => [2]
},
{
:name => "event10",
:days => [1,2]
}
]
},
{
:heading => "3",
:events => [
{
:name => "event5",
:days => [3]
}
]
},
{
:heading => "4",
:events => [
{
:name => "event6",
:days => [4]
},
{
:name => "event2",
:days => [1,4,5]
}
]
},
{
:heading => "5",
:events => [
{
:name => "event7",
:days => [5]
},
{
:name => "event9",
:days => [1,5]
},
{
:name => "event2",
:days => [1,4,5]
}
]
},
{
:heading => "1-3",
:events => [
{
:name => "event8"
}
]
},
{
:heading => "1.5",
:events => [
{
:name => "event1"
},
{
:name => "event11"
}
]
}
]
It is very advanced Ruby here. Maybe too advanced for me, everything I've tried ends up missing one part of the equation. But hey it's Ruby, it shouldn't be that hard?
EDIT: Updated example with clarification and corrected expected output
require 'pp'
pp(events.inject(Hash.new { |h, k| h[k] = [] }) do |m, e|
days = e[:days]
event = { :name => e[:name] }
if days.size >= 3 && days.last - days.first + 1 == days.size
m["%d-%d" % [days.first, days.last]] << event
else
days.each { |d| m[d.to_s] << event }
end
m
end)
I would modify DigitalRoss's answer slightly. I would change
if days.size >= 3 && days.last - days.first + 1 == days.size
to
if days.size >= 3 && (days.first..days.last).to_a == days
This will catch [1,1,3,4]...
It would also be wise to sort the days array before testing!

Resources