Qqwy
Qqwy

Reputation: 5649

Ruby/Rails how to iterate months over a DateTime range?

I am trying to build a graph from data in a Rails table: The amount of sold products per time-fragment.

Because the graph should be able to show the last hour(in 1-minute steps), the last day (in 1-hour steps), the last week (in 1-day steps), the last month (in 1-day steps), etc, I am trying to reduce the code duplication by iterating over a range of DateTime objects:

# To prevent code-duplication, iterate over different time ranges.
times = {
    :hour=>{newer_than: 1.hour.ago, timestep: :minute},
    :day=>{newer_than: 1.day.ago, timestep: :hour},
    :week=>{newer_than: 1.week.ago, , timestep: :day},
    :month=>{newer_than: 1.week.ago, , timestep: :day}
}


products = Product.all

# Create symbols `:beginning_of_minute`, `:beginning_of_hour`, etc. These are used to group products and timestamps by.
times.each do|name, t| 
  t[:beginning_of] = ("beginning_of_" << t[:timestep].to_s).to_sym 
end 

graphs = times.map do |name, t|
   graphpoints = {}
   seconds_in_a_day = 1.day.to_f
   step_ratio = 1.send(t[:timestep]).ago.to_f / seconds_in_a_day
   time_enum = 1.send(t[:timestep]).ago.to_datetime.step(DateTime.now, step_ratio)
   time_enum.each do |timestep|
       graphpoints[time_moment.send(timehash[:beginning_of]).to_datetime] = []
   end

   # Load all products that are visible in this graph size
   visible_products = products.select {|p| p.created_at >= t.newer_than}
   # Group them per graph point
   grouped_products = visible_products.group_by {|item| item.created_at.send(timehash[:beginning_of]).to_datetime}

   graphpoints.merge!(grouped_products)

   {
      points: graphpoints,
      labels: graphpoints.keys
   }
end

This code works great for all time-intervals that have a constant size (hour,day,week). For months, however, it uses a step_ratio of 30 days: 1.month / 1.day == 30. Obviously, the amount of days that months has is not constant. In my script, this has the result that a month might be 'skipped' and therefore missing from the graph.

How can this problem be solved? How to iterate over months while keeping the different amount of days in the months in mind?

Upvotes: 4

Views: 2779

Answers (2)

fanjieqi
fanjieqi

Reputation: 572

if you have to select month over a gigantic arrays, just make the range between two Date:class.

(1.year.ago.to_date..DateTime.now.to_date)).select{|date| date.day==1}.each do |date|
  p date
end

Upvotes: 5

EugZol
EugZol

Reputation: 6555

Use groupdate gem. For example (modified example from the docs):

visible_products = Product.where("created_at > ?", 1.week.ago).group_by_day

# {
#   2015-07-29 00:00:00 UTC => 50,
#   2013-07-30 00:00:00 UTC => 100,
#   2013-08-02 00:00:00 UTC => 34
# }

Also, this will be much faster, because your grouping/counting will be done by database itself, without the need to pass all the records via Product.all call to your Rails code, and without the need to create ActiveRecord object for each one (even irrelevant).

Upvotes: 2

Related Questions