Rails 5: group datetime in hash of year, month, day - ruby-on-rails

I have this query:
dates = MyModel.pluck("distinct date(datetime_column)")
Hash[dates.group_by(&:year).map{|y, items| [y, items.group_by{|d| d.month}]}]
which find distinct dates and then gives me this array:
=> {2017=>{12=>[Tue, 12 Dec 2017], 1=>[Sun, 01 Jan 2017]},
2016=>{11=>[Sun, 20 Nov 2016], 12=>[Sat, 24 Dec 2016, Mon, 12 Dec 2016, Fri, 30 Dec 2016]}}
How do I add 3rd level to have hash where there are particular days under each month? Thank you for any help!
Update
If someone needs ordered result, try this for "dates" part:
dates = MyModel.order(:datetime_column).distinct.pluck(:datetime_column)
For better performance can try to use this:
dates = MyModel.order("date_trunc('day', datetime_column) DESC")
.distinct.pluck("date_trunc('day', datetime_column)")
Here is nice blog post on using datetrunc.

You can use each_with_object, but you need to initialize the object correctly. It's a Hash of Hashes of Hashes!
require 'date'
a = Date.today
b = Date.new(2017,1,3)
c = Date.new(2015,12,5)
d = Date.new(2015,11,7)
dates = [a,b,c,d]
hash = Hash.new { |h, y| h[y] = Hash.new { |h2, m| h2[m] = {} } }
dates_by_ymd = dates.each_with_object(hash) do |date, h|
h[date.year][date.month][date.day] = date
end
require 'pp'
pp dates_by_ymd
# {2017=>
# {1=>
# {7=>#<Date: 2017-01-07 ((2457761j,0s,0n),+0s,2299161j)>,
# 3=>#<Date: 2017-01-03 ((2457757j,0s,0n),+0s,2299161j)>}},
# 2015=>
# {12=>{5=>#<Date: 2015-12-05 ((2457362j,0s,0n),+0s,2299161j)>},
# 11=>{7=>#<Date: 2015-11-07 ((2457334j,0s,0n),+0s,2299161j)>}}}
In your case, you'd write :
dates = MyModel.pluck("distinct date(datetime_column)")
hash = Hash.new { |h, y| h[y] = Hash.new { |h2, m| h2[m] = {} } }
dates_by_ymd = dates.each_with_object(hash) do |date, h|
h[date.year][date.month][date.day] = date
end
Note that this code returns Date objects as leaves of the nested hash, as you mentioned in your question.
If you want a modified version of your code, you can use this Rails code :
dates.group_by(&:year).map do |y, items|
[y, items.group_by(&:month).map { |m, days| [m, days.index_by(&:day)] }.to_h]
end.to_h

Try
dates = MyModel.pluck("distinct date(datetime_column)")
dates.group_by(&:year).map do |y, items|
[y, items.group_by(&:month).map { |m, days| [m, days.map(&:day)] }.to_h]
end.to_h

Related

how to merge json arry in ruby on rails

merge data according to month wise same month revenu added in own month and month name no replicated
[{"revenu":0,"month":"January"},
{"revenu":0,"month":"February"},
{"revenu":0,"month":"March"},
{"revenu":0,"month":"April"},
{"revenu":1832.4430203602753,"month":"May"},
{"revenu":4502.1,"month":"May"},
{"revenu":54.673303657726436,"month":"May"},
{"revenu":0,"month":"June"},
{"revenu":0,"month":"July"},
{"revenu":0,"month":"August"},
{"revenu":0,"month":"September"},
{"revenu":0,"month":"October"},
{"revenu":0,"month":"November"},
{"revenu":0,"month":"December"}]
sample = []
array.each do |data|
sample_hash = {}
check_exi = sample.select {|h| h[:month] == data[:month]}
if check_exi.empty?
sample_hash[:revenu] = data[:revenu]
sample_hash[:month] = data[:month]
sample.push(sample_hash)
else
check_exi[0][:revenu] = check_exi[0][:revenu] + data[:revenu]
end
end
grouped = array.group_by { |d| d[:month] }
new_array = []
grouped.each do |data|
list = {}
sum = 0
month = ""
data[1].each do |e|
sum = sum + e[:revenu]
month = e[:month]
end
list[:revenu] = sum
list[:month] = month
new_array.push(list)
end
new_arr = given_array.group_by{ |data| data[:month]}
new_arr.map{|key, val| {"month": key, "revenu": val.inject(0){|sum, hash| sum + hash[:revenu]}}}
You can make use of group_by to group the data by :month and then use sum to add the revenue.
array
.group_by{ |data| data[:month] }
.map{ |k, v| {month: k, revenu: v.sum { |m| m[:revenu] || 0}} }
#=> [{:month=>"January", :revenu=>0},
#=> {:month=>"February", :revenu=>0},
#=> {:month=>"March", :revenu=>0},
#=> {:month=>"April", :revenu=>0},
#=> {:month=>"May", :revenu=>6389.216324018002},
#=> {:month=>"June", :revenu=>0},
#=> {:month=>"July", :revenu=>0},
#=> {:month=>"August", :revenu=>0},
#=> {:month=>"September", :revenu=>0},
#=> {:month=>"October", :revenu=>0},
#=> {:month=>"November", :revenu=>0},
#=> {:month=>"December", :revenu=>0}]

Ruby: Sort Dates by their closeness to some test date

I have an array of dates. I need to sort those dates by their closeness to some test_date. The dates in the array can be before or after that test_date.
My simple ruby program isn't getting it right. My strategy is to convert each Date to a unix timestamp. Then I do a custom sort by the absolute value of the difference between the dates and the test_date.
Here is that program:
require 'date'
test_date = DateTime.new(2017,2,3,4,5,6)
date1 = Date.new(2017,1,6)
date2 = Date.new(2017,1,5)
date3 = Date.new(2017,2,5)
date4 = Date.new(2017,2,6)
date5 = Date.new(2017,2,9)
date6 = Date.new(2017,2,1)
date7 = Date.new(2017,2,2)
dates_ary = date1, date2, date3, date4, date5, date6, date7
sorted_dates = dates_ary.sort do |d1, d2|
if( (d1.to_time.to_i - test_date.to_time.to_i).abs <= (d2.to_time.to_i - test_date.to_time.to_i).abs)
d1 <=> d2
else
d2 <=> d1
end
end
puts "expected order: "
puts "2017-02-02"
# pointed out in comments that 2017-02-05 will be closer than 2017-02-01 due to test_date having present hours, minutes, and seconds.
puts "2017-02-05"
puts "2017-02-01"
puts "2017-02-06"
puts "2017-02-09"
puts "2017-01-06"
puts "2017-01-05"
puts ''
puts 'actual order:'
sorted_dates.each {|d| puts d}
Also: if there is some existing ruby or rails method that sorts dates by their closeness to another date, let me know. Thanks!
The if is redundant. Ruby will handle it:
sorted_dates = dates.sort do |d1, d2|
(d1.to_time.to_i - test_date.to_time.to_i).abs <=> (d2.to_time.to_i - test_date.to_time.to_i).abs
end
Better:
sorted_dates = dates.sort_by do |date|
(date.to_time.to_i - test_date.to_time.to_i).abs
end
Better:
sorted_dates = dates.sort_by { |date| (date - test_date).abs }

How to combine array and hash by date

I have an array and a hash. The array contains dates and the hash contains a date as key and a float as value (revenue_green).
This is my array:
#reporting_dates = #month_ends.find_all {|e| e < #current_reporting_date.to_s } <<
#current_reporting_date
And my hash:
#revenue_green = #reports_at_reporting_dates.sum('revenue_green')
Which gives me the following output:
#reporting_dates: ["2017-01-27", "2017-02-24", Fri, 10 Mar 2017]
#revenue_green: {Fri, 10 Mar 2017=>7.0}
For two dates of #reporting_dates (2017-01-27 and 2017-02-24) #revenue_green does not have any values.
I want to create a hash that gives me the following output:
#new_hash: {2017-01-27 => 0, 2017-02-24 => 0, 2017-03-10 => 7.0}
so for all dates where no revenue_green exists it should put a 0.
How can I do that?
UPDATE
#month_ends = ["2017-01-27", "2017-02-24", "2017-03-31", "2017-04-28",
"2017-05-26", "2017-06-30", "2017-07-28", "2017-08-25",
"2017-09-29", "2017-10-27", "2017-11-24", "2017-12-29"]
#all_dates = (Date.new(#reporting_year).
beginning_of_year..1.month.from_now).
to_a.reverse.select { |day| day.wday == 5 }
#current_reporting_date = #all_dates.select { |d| d <= Date.today }.first
#all_reports = Report.all
#reports_at_reporting_dates = #all_reports.where(:day => #reporting_dates).order(:day)
Assuming your starting objects are
#reporting_dates = ["2017-01-27", "2017-02-24", "Fri, 10 Mar 2017"]
#revenue_green = {"Fri, 10 Mar 2017"=>7.0}
(i.e., quotes around "Fri, 10 Mar 2017"), then this should work:
require 'date'
#new_hash = Hash.new
#reporting_dates.each {|d| #new_hash[Date.parse(d)] = #revenue_green[d] || 0}
#new_hash => {#<Date: 2017-01-27 ((2457781j,0s,0n),+0s,2299161j)>=>0, #<Date: 2017-02-24 ((2457809j,0s,0n),+0s,2299161j)>=>0, #<Date: 2017-03-10 ((2457823j,0s,0n),+0s,2299161j)>=>7.0}
or, if you want the keys in the new hash to be strings,
#reporting_dates.each {|d| #new_hash[Date.parse(d).strftime("%Y-%m-%d")] = #revenue_green[d] || 0}
#new_hash => {"2017-01-27"=>0, "2017-02-24"=>0, "2017-03-10"=>7.0}

Merge two arrays into a Hash

My desired outcome is something like this:
{date: 12/02/2014, minutes: 36}
I'm scraping with Nokogiri using:
dates = doc.css('td:nth-child(3)')
minutes = doc.css('td:nth-child(10)')
Then I do some filtering and pushing results into arrays:
dates.each do |x|
if x.text.length == 10
date_array << x.text
end
end
minutes.each do |x|
minutes_array << x.text
end
How can I zip these two arrays together to create my desired outcome?
i've tried something like this, but it's not quite right (gives me {"2013-10-29"=>"32:14"} )
result = Hash[date_array.zip(minutes_array)]
or even something like this:
result = Hash[date_array.zip(minutes_array).map {|d, m| {:date => d, :minutes => m}}
but i get this error: wrong element type Hash at 163
i've also tinkered with .flatten but to no avail. Can anybody help?
assuming you have 2 equal length arrays x and y
x = [:key1, :key2, :key3]
y = [:value1, :value2, :value3]
z = {}
x.each_with_index { |key,index| z[key] = y[index] }
puts z
=> {:key1=>:value1, :key2=>:value2, :key3=>:value3}
is that what you are looking for?
then maybe this:
x = [:key1, :key2, :key3]
y = [:value1, :value2, :value3]
z = []
x.each_with_index { |key,index| z << { date: key, minutes: y[index]} }
puts z
{:date=>:key1, :minutes=>:value1}
{:date=>:key2, :minutes=>:value2}
{:date=>:key3, :minutes=>:value3}
Stealing from nPn (I can't comment on his answer because I've got no reputation )
Assuming you have
x = [ "date1", "date2", "date3"]
y = [ "time1", "time2", "time3"]
Then you can do:
z = []
x.each_with_index { |k, i| z << { date: k, time: y[i] } }
puts z
=> [ { date: "date1", time: "time1" },
{ date: "date2", time: "time2" },
{ date: "date3", time: "time3" } ]
Is this what you are looking for ?
You are trying to have the same key (date, minutes) for multiple values. You can instead have an array of hash for all those date-minute combos though, with this -
date.zip(minutes).reduce([]) { |memo, combo| memo << Hash[*[:date, :minutes].zip(combo).flatten] }
Here is how it looks -
2.1.5 :035 > date=["10/10,2010","11/10/2010","12/10/2010","13/10/2010","14/10/2010"]
=> ["10/10,2010", "11/10/2010", "12/10/2010", "13/10/2010", "14/10/2010"]
2.1.5 :036 > minutes = [10,20,30,40,50]
=> [10, 20, 30, 40, 50]
2.1.5 :037 > date.zip(minutes).reduce([]) { |memo, combo| memo << Hash[*[:date, :minutes].zip(combo).flatten] }
=> [{:date=>"10/10,2010", :minutes=>10}, {:date=>"11/10/2010", :minutes=>20}, {:date=>"12/10/2010", :minutes=>30}, {:date=>"13/10/2010", :minutes=>40}, {:date=>"14/10/2010", :minutes=>50}]
2.1.5 :038 >
Word of caution - you should really use a Struct, and then create an array of that Struct instances, instead of working on arrays of hashes like this.
If
dates = ["12/02/14", "6/03/14"]
minutes = [12, 19]
then if I've not misunderstood the question, it's just:
dates.zip(minutes).map { |d,m| {date: d, minutes: m} }
#=> [{:date=>"12/02/14", :minutes=>12}, {:date=>"6/03/14", :minutes=>19}]

How can I find records from today, yesterday and so on with Ruby on Rails?

I want to find all records, say Posts, created today with Ruby on Rails, then all Posts created yesterday, and so on… how should I do?
Thank you,
Kevin
Try this:
#Today
Posts.find(:all, conditions: { :created_at => Date.today...Date.today + 1 })
#Yesterday
Posts.find(:all, conditions: { :created_at => Date.today - 1...Date.today })
Or this (preferable, in my opinion):
#Today
Posts.find(:all, conditions: ["DATE(created_at) = ?", Date.today] )
#Yesterday
Posts.find(:all, conditions: ["DATE(created_at) = ?", Date.today - 1] )
As a rule I store all dates on my server in UTC timezone and let the UI handle any timezone conversion.
To get the sort of query you are after to work correctly I had to massage the incoming date into a
UTC specific time range first.
require 'date'
class Post < ActiveRecord::Base
def self.created(a_date)
return Post.where(created_at: to_timerange(a_date))
end
private
def self.to_timerange(a_date)
raise ArgumentError, "expected 'a_date' to be a Date" unless a_date.is_a? Date
dts = Time.new(a_date.year, a_date.month, a_date.day, 0, 0, 0).utc
dte = dts + (24 * 60 * 60) - 1
return (dts...dte)
end
end
This then allows you to call
# today
posts = Post.created(Date.today)
# yesterday
posts = Post.created(Date.today - 1)
To query using a range I prefer the following:
yesterday = Date.yesterday
start = yesterday.beginning_of_day
#Fri, 27 Nov 2020 00:00:00 UTC +00:00
end = yesterday.end_of_day
# Fri, 27 Nov 2020 23:59:59 UTC +00:00 - the value here is one second before midnight
# meaning we should use an inclusive range using two dots:
range = start..end
Post.where(created_at: range)

Resources