#93 Recurring Events with ice_cube
8/13/2017

Summary

ice_cube is a ruby library for easily handling repeated events and schedules.
4
rails schedules 6:34 min

Summary

Gemfilegem 'ice_cube'
event.rbclass Event < ApplicationRecord
  enum occurrence: { biweekly: 0, monthly: 1, annually: 2 }

  def schedule
    # Not covered in the video, but in these situations,
    # I'll most likely call schedule multiple times in a 
    # complex view, so I will use memoization for the schedule
    @schedule ||= begin
      schedule = IceCube::Schedule.new(now = start_date)
      case occurrence      
      when 'biweekly'
        schedule.add_recurrence_rule IceCube::Rule.weekly(2)
      when 'monthly'
        schedule.add_recurrence_rule IceCube::Rule.monthly(1)
      when 'annually'
        schedule.add_recurrence_rule IceCube::Rule.yearly(1)
      end
      schedule
    end
  end
end
rails consolestart_date = Date.new(2017,1,1)
end_date = Date.new(2017,12,31)
event = Event.create(name: '2017 Calendar', start_date: start_date, end_date: end_date)
event.schedule.occurrences(end_date)

event.schedule
    => #[#], :base_wday=>[#], :base_hour=>[#], :base_min=>[#], :base_sec=>[#]}, @interval=2, @week_start=:sunday, @time=nil, @start_time=nil, @uses=0>], @all_exception_rules=[]>

event.schedule.occurrences(end_date)
    => [2017-01-01 00:00:00 -0500, 2017-01-15 00:00:00 -0500, 2017-01-29 00:00:00 -0500, 2017-02-12 00:00:00 -0500, 2017-02-26 00:00:00 -0500, 2017-03-12 00:00:00 -0500, 2017-03-26 00:00:00 -0400, 2017-04-09 00:00:00 -0400, 2017-04-23 00:00:00 -0400, 2017-05-07 00:00:00 -0400, 2017-05-21 00:00:00 -0400, 2017-06-04 00:00:00 -0400, 2017-06-18 00:00:00 -0400, 2017-07-02 00:00:00 -0400, 2017-07-16 00:00:00 -0400, 2017-07-30 00:00:00 -0400, 2017-08-13 00:00:00 -0400, 2017-08-27 00:00:00 -0400, 2017-09-10 00:00:00 -0400, 2017-09-24 00:00:00 -0400, 2017-10-08 00:00:00 -0400, 2017-10-22 00:00:00 -0400, 2017-11-05 00:00:00 -0400, 2017-11-19 00:00:00 -0500, 2017-12-03 00:00:00 -0500, 2017-12-17 00:00:00 -0500, 2017-12-31 00:00:00 -0500]

event.schedule.previous_occurrence(Date.today)
    => 2017-07-30 00:00:00 -0400

event.schedule.previous_occurrence(Date.today)
    => 2017-07-30 00:00:00 -0400

event.schedule.next_occurrence(Date.today)
    => 2017-08-27 00:00:00 -0400

event.schedule.next_occurrences(4, Date.today)
    => [2017-08-27 00:00:00 -0400, 2017-09-10 00:00:00 -0400, 2017-09-24 00:00:00 -0400, 2017-10-08 00:00:00 -0400]


00000000000000000000000000000000?d=mm&f=y&s=64
vitkoz said 3 months ago:

Very useful, thank you!

59744?v=4&s=64
schneems said 3 months ago:

Would love to see a video on how to do notifications based on future events. Could be recurring or not. For example in gmail I can schedule to get notified by email 10 minutes before an event. They're pretty spot on. Would love to see your take on how something like that could be built.


635114?v=3&s=64
kobaltz PRO said 3 months ago:

Sounds interesting. I'll put this on my list of episodes to cover.

At a first glance, I would approach it a few different ways:

1. use sidekiq-cron and create a scheduled task to monitor all of the "notifications" that need to be sent out. Downfall is that as the application grows, even a well indexed table could start to get slow to query.

2. use sidekiq to tie into activejob's perform at. With sidekiq it would look something like this

Job.set(wait_until: send_at_this_time).perform_later(something)

And the benefit of this approach would be that the query wouldn't need to happen since the job is already queued to execute at a later time. The downfall of this could be an improperly configured Redis instance (memory set to volatile instead of nonvolatile) which could result in missing jobs.

If data integrity was an must, I typically prefer persistent queues like delayed_job over in memory. However, this is questionable on "data integrity", but it makes me feel better inside that critical data is not lost in a memory/persistent (write cache to disk) queue.


Regardless, I think option 2 would be a better way to go, there would just have to be some sort of hook to remove the job if the scheduled event were cancelled.

4278?v=4&s=64
avit said 3 months ago:

Although it's possible to use IceCube with Date instances like you've shown when selecting the "start" and "end" dates, IceCube actually works with exact Time objects and will convert it for you internally. You can see this in the occurrence output, where it defaults to 00:00 of your local system time zone. (This might turn out to be UTC on your server, and different from your workstation, or it could be affected by the current `Time.zone` setting in Rails... it might be what you expect, or maybe not. Just be aware.)

If you're thinking of using the schedules for anything like reminders, you will have more accurate results if you build your schedule with an actual Time object instead of a Date. When using Rails, you can get a local time (TimeWithZone) for any zone like this:

# Get a zone instance, (or else set it globally via Time.zone):
zone = ActiveSupport::TimeZone["America/New_York"]

# Get any time in that zone, or parse from a string:
now = zone.local(2017, 9, 1, 12, 0, 0)
now = zone.parse("2017-09-01 12:00:00")

# Then use it with the schedule:
schedule = IceCube::Schedule.new(now)

IceCube will then correctly handle things like Daylight Saving Time changes.

Login to Comment