I'm trying to write a method to return a very specific data structure for a chart that I am populating.
A user can enter dates for miles that they've hiked, so on the Mile model I have start_date and end_date attributes.
The method I have currently is close, however it should return a 0 for the current month because the user hasn't entered any miles hiked for the month of May. I also need to constrain it to the current year, which it currently is not.
Here is what my current method looks like:
def miles_by_month
miles = self.miles.from_this_year.group_by { |t| t.start_date.strftime('%b') }
total_miles = miles.map do |m|
{ 'indicator': m[0], 'total': m[1].sum(&:length) }
end
total_miles
end
And the 'from_this_year' scope on the Mile model if it's helpful:
scope :from_this_year, lambda { where("start_date > ? AND start_date < ?", Time.now.beginning_of_year, Time.now.end_of_year) }
And here is an example of what it returns:
[
[0] {
:indicator => "Jan",
:total => 15
},
[1] {
:indicator => "Feb",
:total => 10
},
[2] {
:indicator => "Mar",
:total => 10
},
[3] {
:indicator => "Apr",
:total => 100
},
[4] {
:indicator => "May", # I need [4] to show up with
:total => 0 # a total of 0. [4] currently
} # does not show up at all.
]
"Indicator" refers to the name of the month, and "total" refers to a sum of the number of miles that user has hiked for that specific month.
Any suggestions?
UPDATE 1
I have modified my miles_by_month
method somewhat based on an answer here to the following:
def miles_by_month
months = Date::ABBR_MONTHNAMES[1..12]
miles = self.miles.from_this_year.group_by { |t| t.start_date.strftime('%b') }
total_miles = miles.map do |m|
{ 'indicator': m[0], 'total': m[1].sum(&:length) }
end
months.each do |month|
unless total_miles.any? { |r| r[:indicator] == month }
total_miles.push(indicator: month, total: 0)
end
end
total_miles
end
The only remaining thing I need to do is figure out how to constrain the months variable from the start of the year to the current month.
Based on Max Pleaner's answer, I was able to come up with the following method to correctly return what I needed:
def miles_by_month
cur_month = Time.now.month
months = Date::ABBR_MONTHNAMES[1..cur_month]
miles = self.miles.from_this_year.group_by { |t| t.start_date.strftime('%b') }
total_miles = miles.map do |m|
{ 'indicator': m[0], 'total': m[1].sum(&:length) }
end
months.each do |month|
unless total_miles.any? { |r| r[:indicator] == month }
total_miles.push(indicator: month, total: 0)
end
end
total_miles
end
This returns a data structure like the following:
[
[0] {
:indicator => "Jan",
:total => 15
},
[1] {
:indicator => "Feb",
:total => 10
},
[2] {
:indicator => "Mar",
:total => 10
},
[3] {
:indicator => "Apr",
:total => 100
},
[4] {
:indicator => "May",
:total => 0
}
]
It's certainly not the most efficient method and I'd be very curious to see if anyone has more efficient ways of handling this.