Loop through array of hashes in rails - ruby-on-rails

I have the following array of hashes that I send to my controller:
comparisons = [{
"startdate": "01/01/2016",
"enddate": "01/02/2016"
}, {
"startdate": "01/03/2016",
"enddate": "01/04/2016"
}, {
"startdate": "01/05/2016",
"enddate": "01/06/2016"
}, {
"startdate": "01/05/2016",
"enddate": "01/06/2016"
}];
$.ajax({
url: '/get_data',
method: 'GET',
dataType: 'JSON',
data: {
startdate: '01/01/2016',
enddate: '01/01/2016',
comparisons: comparisons
},
success: function (data) {
console.log(data);
}
});
And then in the controller I want to loop through these comparisons:
#comparisons = []
if !params[:comparisons].blank?
params[:comparisons].each do |c|
#comparisons.push({:startdate => c[:startdate], :enddate => c[:enddate], :data => get_data(c[:startdate], c[:enddate], 'week')})
end
end
I get an error: > no implicit conversion of Symbol into Integer
And when debugging I'm finding that the c in my loop isn't structured the same as what I'm sending...
c: ["0", {"startdate"=>"01/01/2016", "enddate"=>"01/01/2016"}]
How can I fix this?

easiest fix would be to change how you refer to the c data... you can see that c is an array (explains the error, it's expecting array elements to be accessed by an integer) and you can see the second (last) element of the c array has the data you want.
#comparisons.push({:startdate => c.last[:startdate],
:enddate => c.last[:enddate],
:data => get_data(c.last[:startdate], c.last[:enddate], 'week')})

Related

Consolidating a Ruby Array of Hashes

I'm interning at a company right now, and I have a database that I connect to in order to get some specific data about our customers. The query is all worked out, the database is returning the data I need, but now I need to figure out how to consolidate the data into the necessary format. We are using Ruby 3.1.2 for reference.
This is the format that I receive from our database (the real data will be much larger, so I'm only using a small dataset until the logic is solid).
[{ "product_id"=>1, "customer_id"=>001 },
{ "product_id"=>2, "customer_id"=>001 },
{ "product_id"=>1, "customer_id"=>002 },
{ "product_id"=>3, "customer_id"=>002 },
{ "product_id"=>1, "customer_id"=>003 },
{ "product_id"=>2, "customer_id"=>003 },
{ "product_id"=>3, "customer_id"=>003 }]
When I get this data, I need to get a list of each distinct "customer_id" with a list of every "product_id" they have assigned to them. Example of what I need to get back below.
{001=>[1, 2], 002=>[1, 3], 003=>[1, 2, 3]}
I thought I had a solution with the line below, but it doesn't seem to work how I expected.
data.group_by(&:customer_id).transform_values { |p| p.pluck(:product_id) }
In addition to #group_by one might use #each_with_object to iteratively build the needed hash.
data.each_with_object({}) { |x, h|
h[x["customer_id"]] ||= []
h[x["customer_id"]] << x["product_id"]
}
# => {1=>[1, 2], 2=>[1, 3], 3=>[1, 2, 3]}
I would do this:
array = [{ "product_id"=>1, "customer_id"=>001 },
{ "product_id"=>2, "customer_id"=>001 },
{ "product_id"=>1, "customer_id"=>002 },
{ "product_id"=>3, "customer_id"=>002 },
{ "product_id"=>1, "customer_id"=>003 },
{ "product_id"=>2, "customer_id"=>003 },
{ "product_id"=>3, "customer_id"=>003 }]
array.group_by { |hash| hash['customer_id'] }
.transform_values { |values| values.map { |value| value['product_id'] } }
#=> { 1 => [1, 2], 2 => [1, 3], 3 => [1, 2, 3] }
The other answers are correct in their own ways but in my opinion code-readability should also always be considered and considering that factor the accepted answer looks more nearer and my solution below is also based on the same logic with the only difference that I have moved out the string keys outside the loop because the string literal inside the loop should create, in each iteration, different String instances in memory for those keys.
array = [{ "product_id"=>1, "customer_id"=>001 },
{ "product_id"=>2, "customer_id"=>001 },
{ "product_id"=>1, "customer_id"=>002 },
{ "product_id"=>3, "customer_id"=>002 },
{ "product_id"=>1, "customer_id"=>003 },
{ "product_id"=>2, "customer_id"=>003 },
{ "product_id"=>3, "customer_id"=>003 }]
transformed_data = {}
customer_id_key = "customer_id"
product_id_key = "product_id"
array.each do |h|
customer_id = h[customer_id_key]
product_id = h[product_id_key]
transformed_data[customer_id] ||= []
transformed_data[customer_id] << product_id
end
transformed_data

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

proper syntax for generating JSON body in API call

With the following long controller action code
#available = Available.find(694)
#tareservation_id = 8943
#request_date_time = Time.now.utc.iso8601
#request_id = Time.now.to_i
#in_date = (Date.today + 24.days).strftime("%Y-%m-%d").to_s
#book = %Q|{
"booking": {
"currencyCode": "USD",
"languageCode": "es",
"paxNationality": "ES",
"clientRef": {
"value": \"#{#tareservation_id}\",
"mustBeUnique": true
},
"items": [
{
"itemNumber": 1,
"immediateConfirmationRequired": true,
"productCode": \"#{#available.product_code}\",
"leadPaxName":
{ "firstName": "Guy",
"lastName": "Test"
},
"product":
{
"period":
{
"start": "2018-08-27",
"quantity": 2
}
}
} ]
},
"requestAuditInfo":
{ "agentCode": "001",
"requestPassword": "pass",
"requestDateTime": \"#{#requestDateTime}\",
"requestID": #{#request_id} },
"versionNumber": "2.0"
}|
This then must be shipped off to the API as JSON in the body call
#result = HTTParty.post(
'https://test.com/search',
:body => JSON.parse(#book).to_json,
headers: {
'Content-Type' => 'application/json',
'Accept' => 'application/json',
'Connection' => 'Keep-Alive'
}
)
If the following block is removed:
,
"product":
{
"period":
{
"start": "2018-08-27",
"quantity": 2
}
}
in console JSON.parse(#start), parses properly. With the block JSON::ParserError: 784: unexpected token. Yet I fail to see what is incorrect here?
Is Rails handling of string for future JSON conversion really strict on syntax, particularly since there is interpretation of instance variables - both as strings and integers - and har returns involved? What would it be then? Or is there a safer solution to get out of what quickly becomes quicksand?
It turns out that pasting too many lines into the console (iTerm2, in this case) does something to the memory. 25 lines of code pasted in a single time is the maximum observered where behaviour is as expected.

Is there a specific way to add data points from fetch() after a chart has been built?

I have data which I fetch() and add to a time series chart once they are available. The received data are in the format
[
{
"count": 1932,
"date": "2018-03-29T00:00:00"
},
{
"count": 3957,
"date": "2018-03-28T00:00:00"
},
{
"count": 3864,
"date": "2018-03-27T00:00:00"
},
]
and I use addPoint() to update the chart.
The chart is correctly updated with the Y ("count") but the X axis is ticked by seconds.
This would suggest a wrong format of the time data. However, the following code (which builds the chart)
let chart = Highcharts.chart("app", {
xAxis: {
type: "datetime"
},
series: [
{
name: "candidates",
data: [],
},
{
name: "scanned",
data: [],
},
],
})
// get candidates
fetch("https://example.com/rpc/candidat_per_day", {
method: "POST",
body: JSON.stringify({
"limit_days": 15
}),
headers: new Headers({
'content-type': 'application/json'
}),
})
.then(r => r.json())
.then(r => {
r.reverse().pop()
r.forEach(data => {
console.log(moment(data.date).format("x"))
chart.series[0].addPoint([moment(data.date).format("x"), data.count])
})
})
fetch("https://example.com/rpc/scanned_per_day", {
method: "POST",
body: JSON.stringify({
limit_days: 15,
}),
headers: new Headers({
"content-type": "application/json",
}),
})
.then(r => r.json())
.then(r => {
r.reverse().pop()
r.forEach(data => {
d = moment(data.date).format("x")
console.log(d)
chart.series[1].addPoint([d, data.count])
})
})
outputs on the console numbers such as 1521414000000 or 1521500400000. When converting them independently I get respectively the 18 and 19 of March 2018 - the expected date for these data with the confirmation that they are in ms.
Worse: when trying to replicate the issue by adding these points to an existing chart, everything is OK:
let chart = Highcharts.chart('container', {
xAxis: {
type: 'datetime'
},
series: [{
data: []
}]
});
chart.series[0].addPoint([1519772400000, 5])
chart.series[0].addPoint([1520895600000, 6])
chart.series[0].addPoint([1521241200000, 4])
<script src="https://code.highcharts.com/highcharts.js"></script>
<div id="container"></div>
The difference I see between the replication code and the one I actaully run is the fetch() part (and the promises handling) but I do not see any reasons for the code not to work that way.
I believe there is a minor issue somewhere (probably around the time handling), but after staring at the code for hours I cannot find anything wrong. What should I try next?
The problem here is that format() function returns a String:
// in console:
moment().format("x")
"1522407551509"
Highcharts expect the x coordinate to be a Number.
You can use parseInt() for the conversion:
console.log(parseInt(moment().format("x")))
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.21.0/moment.min.js"></script>

how to get value from json file using jquery autocomplete

This is my jquery code
$('.typeahead', this).autocomplete({
source: function(request, response) {
$.ajax({
url: 'includes/stations.json',
dataType: 'json',
data: request,
success: function(data) {
response($.map(data, function(item, i) {
return {
label: item.name,
value: item.code
}
}));
},
});
},
minLength: 3
});
My Json File is
[
{
"code": "9BP3",
"name": "9Bp No3"
},
{
"code": "AA",
"name": "Ataria"
},{
"code": "BILA",
"name": "Bheslana"
},
{
"code": "BILD",
"name": "Bildi"
},{
"code": "HRI",
"name": "Hardoi"
},
{
"code": "HRM",
"name": "Hadmadiya"
}
]
When i typing any three letter its returns whole json file values
q: request.term this is the string for filter returned values from stations.json. You must pass variable like this to filter results. stations.json must be dynamically generated file like php with json header. q is $_GET parameter and must be parsed. Try to change the code like this:
$.ajax({
url: "includes/stations.json",
dataType: "json",
data: {
q: request.term // here is the string for filter returned values from stations.json. You must pass variable like this to filter results. stations.json must be dynamically generated file like php with json header. q is $_GET parameter and must be parsed.
},
success: function(data) {
response(
$.map(data, function(item, i) {
return {
label: item.name,
value: item.code
}
})
);
}
});
},
minLength: 3,

Resources