Sort Nested Hash Inside Array - ruby-on-rails

I have sample data like this:
[
[{"calendar"=>{:start_date=>Thu, 07 Aug 2014, :title=>"Recurring Event Test", :has_downloads=>false, :description=>"<p>Recurring content</p>\r\n", :location=>"Lunch hall", :id=>243, :end_date=>Thu, 07 Aug 2014}}],
[{"calendar"=>{:start_date=>Wed, 06 Aug 2014, :title=>"Single event", :has_downloads=>false, :description=>"<p>for date 6th</p>\r\n", :location=>"chennai", :id=>253, :end_date=>Wed, 06 Aug 2014}}]
]
Need to sort this values by start_date field.
I tried like this
sort_by {|vn| vn[:start_date]}
its showing error nil class

vn is an Array and start_date is nested inside calendar. You should do
arr.sort_by {|vn| vn[0]["calendar"][:start_date]}

Related

Group by month and week

I would like to know what is the best way to group my hash of
{date->value} in month and week.
{Sat, 23 Apr 2016=>6.0, Mon, 06 Mar 2017=>9.0, Tue, 04 Apr 2017=>13.0, Tue, 11 Apr 2017=>25.0}
I would like a result like:
{Apr 2016=>6, Mar 2017=>9, Apr 2017=>38}
I tried with Groupdate/Chartkick but I am not getting a good result.
Thanks
Given the desired result for the OP's example, I've assumed that the OP means "month and year" rather than by "week and month".
I also assumed that the keys of the input hash are intended to be strings (rather than date objects) and the keys of the hash returned are intended to be strings. In any event, I would use Hash::new with a default value of zero (a counting hash). See the doc for details.
dates = {"Sat, 23 Apr 2016"=>6.0, "Mon, 06 Mar 2017"=>9.0,
"Tue, 04 Apr 2017"=>13.0, "Tue, 11 Apr 2017"=>25.0}
dates.each_with_object(Hash.new(0)) { |(k,v),h| h[k[-8..-1]] += v.to_i }
#=> {"Apr 2016"=>6, "Mar 2017"=>9, "Apr 2017"=>38}
If the dates of the input hash are date objects, we can do the following. First create the date objects.
require 'date'
dates = {"Sat, 23 Apr 2016"=>6.0, "Mon, 06 Mar 2017"=>9.0,
"Tue, 04 Apr 2017"=>13.0, "Tue, 11 Apr 2017"=>25.0}.
map { |str, v| [Date.parse(str), v] }.to_h
#=> {#<Date: 2016-04-23 ((2457502j,0s,0n),+0s,2299161j)>=>6.0,
# #<Date: 2017-03-06 ((2457819j,0s,0n),+0s,2299161j)>=>9.0,
# #<Date: 2017-04-04 ((2457848j,0s,0n),+0s,2299161j)>=>13.0,
# #<Date: 2017-04-11 ((2457855j,0s,0n),+0s,2299161j)>=>25.0}
Then we can write
dates.each_with_object(Hash.new(0)) { |(k,v),h| h[k.strftime("%b %Y")] += v.to_i }
#=> {"Apr 2016"=>6, "Mar 2017"=>9, "Apr 2017"=>38}
See DateTime#strftime.
This is a pure-Ruby solution.
If you have the hash of data (as a hash) already an option would be to use reduce
for the month it would be
data.reduce({}) do |memo, (date, value)|
new_date = date.beginning_of_month.strftime("%B %Y")
memo[new_date] ||= 0
memo[new_date] += value
memo
end
for the week it would be
data.reduce({}) do |memo, (date, value)|
new_date = date.beginning_of_week.strftime("%B %e %Y")
memo[new_date] ||= 0
memo[new_date] += value
memo
end
You can use this document to modify the strftime format however you want it.
Even easier: data.transform_keys! { |k| k.strftime("%B %Y") }

How to join points by line?

I am using chartkick and gem "groupdate" in my project.
<%= area_chart SugarLevel.group_by_minute(created_at).sum(:mmol) %>
I want to keep continuing line from one point to another. I do something wrong with groupdate. If there is no data at some minute, the line has to start from previous point and has to finish at next point.
It has to be graph without gaps. Like that:
Method in gem "groupdate" group_by_something uses Hash to store a data. For example:
#hash = SugarLevel.group_by_minute(:created_at).sum(:mmol) # => {Thu, 01 Jun 2017 17:39:00 UTC +00:00=>#<BigDecimal:2add9a8,'0.77E1',18(18)>, Thu, 01 Jun 2017 17:40:00 UTC +00:00=>0, Thu, 01 Jun 2017 17:41:00 UTC +00:00=>0, Thu, 01 Jun 2017 17:42:00 UTC +00:00=>0, Thu, 01 Jun 2017 17:43:00 UTC +00:00=>0}
We can clear this hash as we want:
#hash.select{|k, v| v != 0} # Select all keys which do not consist 0
And eventually, we can put itin a view: <%= area_chart #hash %>

Select object based on date range

Tech specs: ruby 2.1.5p273, Rails 4.2.3.
I have an array of Days that I want to loop through to pick the right Exits (model) that fall within a date range.
#exits has :start_date and :end_date
#days is an array of dates like:
=> [Sun, 06 Sep 2015, Sat, 12 Sep 2015, Tue, 15 Sep 2015, Fri, 18 Sep 2015, Sat, 19 Sep 2015, Sun, 20 Sep 2015, Wed, 23 Sep 2015]
I thought something like this would work:
#days.each do |day|
#exits.where(:start_date..:end_date).include?(day)
end
but I get an error:
TypeError: Cannot visit Range
What is the best way to query an object that has a date range (between two fields) by comparing it against a single date? Thanks!
You can use the following:
#days.each do |day|
exits = Exit.where('? BETWEEN start_date AND end_date', day)
# etc.
end
If you don't want to loop over them then you can do:
Event.where("start_date IN (:days) AND end_date IN (:days)", { days: #days })
or
Event.where(start_date: #days, end_date: #days)
Exit.where(day: #exit.start_date..#exits.end_date)
or
Exit.where('day >= ? AND day <= ?', #exit.start_date, #exits.end_date)
Doing SQL queries in a loop is probably a bad idea, it could be refactored to be be one call most likely. And this should happen in the controller not in the view.

ActiveSupport::JSON::Encoding::CircularReferenceError in content_tag

I have my User model with the following functions:
def self.chart_data(start = 1.weeks.ago)
total_users = users_by_day(start)
(start.to_date..Date.today).map do |date|
{
created_at: date,
users: total_users[date] || 0
}
end
end
def self.users_by_day(start)
users = where(created_at: start.beginning_of_day..Time.zone.now)
users = users.group("date(created_at), id")
users = users.select("created_at, count(id) as total_users")
users.each_with_object({}) do |user, total_users|
total_users[user.created_at.to_date] = total_users
end
end
And my View has the following line:
<%= content_tag :div, "", id: "users_chart", data: {users: User.chart_data} %>
I get the following error:
ActiveSupport::JSON::Encoding::CircularReferenceError in Static#statistics
Showing statistics.html.erb where line #5 raised:
object references itself
When i i just print the data it looks as the following:
[{:created_at=>Wed, 27 Nov 2013, :users=>0}, {:created_at=>Thu, 28 Nov 2013, :users=>0}, {:created_at=>Fri, 29 Nov 2013, :users=>0}, {:created_at=>Sat, 30 Nov 2013, :users=>0}, {:created_at=>Sun, 01 Dec 2013, :users=>0}, {:created_at=>Mon, 02 Dec 2013, :users=>0}, {:created_at=>Tue, 03 Dec 2013, :users=>{Tue, 03 Dec 2013=>{...}}}, {:created_at=>Wed, 04 Dec 2013, :users=>0}]
The only user is on 03 Dec, if there are no users there is also no error.
As you'll note in the printed data, there is a recursive reference on the 3rd:
{:created_at=>Tue, 03 Dec 2013, :users=>{Tue, 03 Dec 2013=>{...}}}
The origins come from how users_by_day is constructed. The has building references itself, by assigning an object on a given day to itself:
users.each_with_object({}) do |user, total_users|
total_users[user.created_at.to_date] = total_users
end
I think this is just an oversight in variable naming. The aggregate object you are creating is called total_users by the block, and it's the same name you give it inside the query. I believe the correct block would be this:
users.each_with_object({}) do |user, total_users|
total_users[user.created_at.to_date] = user.total_users
end
Since you need to get the total off the user variable.

Is it possible to create a list of months between two dates in Rails

I am trying to create a page to display a list of links for each month, grouped into years. The months need to be between two dates, Today, and The date of the first entry.
I am at a brick wall, I have no idea how to create this.
Any help would be massively appriciated
Regards
Adam
Just put what you want inside a range loop and use the Date::MONTHNAMES array like so
(date.year..laterdate.year).each do |y|
mo_start = (date.year == y) ? date.month : 1
mo_end = (laterdate.year == y) ? laterdate.month : 12
(mo_start..mo_end).each do |m|
puts Date::MONTHNAMES[m]
end
end
The following code will add a months_between instance method to the Date class
#!/usr/bin/ruby
require 'date'
class Date
def self.months_between(d1, d2)
months = []
start_date = Date.civil(d1.year, d1.month, 1)
end_date = Date.civil(d2.year, d2.month, 1)
raise ArgumentError unless d1 <= d2
while (start_date < end_date)
months << start_date
start_date = start_date >>1
end
months << end_date
end
end
This is VERY lightly tested, however it returns an Array of dates each date being the 1st day in each affected month.
I don't know if I've completely understood your problem, but some of the following might be useful. I've taken advantage of the extensions to Date provided in ActiveSupport:
d1 = Date.parse("20070617") # => Sun, 17 Jun 2007
d2 = Date.parse("20090529") #=> Fri, 29 May 2009
eom = d1.end_of_month #=> Sat, 30 Jun 2007
mth_ends = [eom] #=> [Sat, 30 Jun 2007]
while eom < d2
eom = eom.advance(:days => 1).end_of_month
mth_ends << eom
end
yrs = mth_ends.group_by{|me| me.year}
The final line uses another handy extension: Array#group_by, which does pretty much exactly what it promises.
d1.year.upto(d2.year) do |yr|
puts "#{yrs[yr].min}, #{yrs[yr].max}"
end
2007-06-30, 2007-12-31
2008-01-31, 2008-12-31
2009-01-31, 2009-05-31
I don't know if the start/end points are as desired, but you should be able to figure out what else you might need.
HTH
Use the date_helper gem which adds the months_between method to the Date class similar to Steve's answer.
xmas = Date.parse("2013-12-25")
hksar_establishment_day = Date.parse("2014-07-01")
Date.months_between(xmas,hksar_establishment_day)
=> [Sun, 01 Dec 2013, Wed, 01 Jan 2014, Sat, 01 Feb 2014, Sat, 01 Mar 2014, Tue, 01 Apr 2014, Thu, 01 May 2014, Sun, 01 Jun 2014, Tue, 01 Jul 2014]

Resources