简体   繁体   中英

Ruby - group_by month for current year, including months with no data

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM