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.
You can do this by looping over each of the months and setting a default value, for example:
MONTHS = Date::ABBR_MONTHNAMES[1..12]
# put this somewhere
MONTHS.each do |month|
unless results.any? { |r| r[:indicator] == month }
results.push(indicator: month, total: 0)
end
end
# you can sort them chronologically if needed
results.sort_by! { |r| MONTHS.index(r[:indicator]) }
Note this is not the most efficient code - it has some O(N^2) stuff which can be optimized to O(N) - but it should hopefully give you a starting point
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.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.