Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ruby: Experience with Gems for recurring calendar events? [closed]

I want to find a Ruby gem that can work with recurring events that satisfies the following requirements:

  • can process patterns like "Weekly on Tuesday and Wednesday", or "Monthly on the last Tuesday"
  • can compute the next occurrence
  • can serialize/deserialize a pattern into a string to store in the database
  • serialization has a stable format (i.e. it will be able to load even after upgrading)
  • work at least with following pattern components: Time, Day of Week, Date of Month, nth day of week in month;
  • can repeat daily, weekly, monthly or with interval of n days, weeks or months
  • can represent a pattern in natural English language
  • can parse a pattern from English (optional)
  • can export to some popular format like iCal (optional)
  • can integrate with other gems/systems for calendar and task management (optional)
  • support of Active Record - parameter parsing, validation (optional)
  • has enough tests, and few bugs, more then 1 user :)
  • has reasonable performance

I found two relevant candidates:

  • Tickle - this can parse English temporal expressions
  • Ice_Cube(+Schedule-Attributes) - this is the most popular and can export to iCal

Could you suggest a gem (or set of gems) and describe how well it (or they) meet the criteria listed?

(And if I've missed some important criteria, please mention them in your answer.)

like image 830
Alexey Avatar asked Mar 14 '11 16:03

Alexey


4 Answers

I end up using Ice_Cube for the following reasons:

  • Most popular
  • can compute next occurence
  • can serialize/deserialize a pattern into string to store in database
  • serialization has stable format (e.i. it will be able to load even after upgrading)
  • work at least with following pattern components: Time, Day Of Week, Date of Month, nth day of week in month;
  • can repeat daily, weekly, monthly or with interval of n days, weeks or months
  • can parse a pattern from English (optional)
  • can export to some popular format like iCal (optional)

These on my criteria are not fullfilled by it:

  • can represent a pattern in natural English language
  • support of Active Record - parameter parsing, validation (optional)

This one is not verified:

  • has reasonable performance

Creating Ice_Cube::Schedule from user input in Rails is not very convinient, but doable:

class EntryForm < FormModel

  include IceCube
  class_eval &ValidatesTimelinessSupport[{:start_date => :datetime}]

  Units = [Day = 'day', Week = 'week']
  Intervals = %w[0 1 2 3 4 5 6 7 8 9]
  Week_Days = [:sunday, :monday, :tuesday, :wednesday, :thursday, :friday, :saturday]

  Days_With_Letters = Week_Days.zip(%w[S M T W T F S])

  attr_accessible_accessors :interval, :unit, :start_date
  attr_accessible_accessors *Week_Days

  def_delegators :@model, :display_title, :schedule_yaml, :schedule_yaml=

  validates_date :start_date, :allow_blank => true
  validates_inclusion_of :unit, :in => Units
  validates_inclusion_of :interval, :in => Intervals
  validates_inclusion_of :complete, :in => %w[0 1], :allow_blank => true
  Week_Days.each { |day| validates_inclusion_of day, :in => %w[0 1], :allow_blank => true }

  before_edit {
    if not schedule_yaml.blank? and hash = YAML::load(schedule_yaml)
      schedule = Schedule.from_hash(hash)
    end

    if schedule and rule = schedule.rrules.first
      @start_date = schedule.start_date

      rule_hash = rule.to_hash
      @interval = rule_hash[:interval]

      case rule
      when DailyRule
        @unit = Day
      when WeeklyRule
        @unit = Week
        rule_hash[:validations][:day].try :each do |day_index|
          send "#{Week_Days[day_index]}=", 1
        end
      end

    else
      @start_date = Date.today
      @interval = 1
      @unit = Day
    end
  }

  before_save {
      sd = @start_date.blank? ?
          Date.today.to_all_day :
          @start_date.parse_date_in_timezone
      i = @interval.to_i
      schedule = Schedule.new(sd)


      rule = case @unit
        when Day
          Rule.daily i
        when Week
          Rule.weekly(i).day(
            *Week_Days.
            select { |day| send(day).to_i == 1 } )
      end

      schedule.add_recurrence_rule(rule)

      self.schedule_yaml = schedule.to_yaml
    end
  }
end
like image 159
Alexey Avatar answered Nov 09 '22 12:11

Alexey


As lovely as Ice_Cube is, it doesn't seem suitable for a large scale scheduling applications where you might have 100,000 + Events that you need to filter through to see what appears on a current day. Think meetup.com

Because it's all serialized into a rule string the only way to filter a major list seems to be something like this:

def self.entries_on(date)
    entries = TimetableEntry.all
    entries.reject{|te|!te.schedule.occurs_on?(date)}
end

Not very efficient. Someone PLEASE CORRECT ME!! hopefully I'm missing a trick?

Also try the above method with a Date that's 100 years in the future... it appears as though ice_cube slows down the further you query away from the current date.

like image 25
Morgz Avatar answered Nov 09 '22 10:11

Morgz


I'm not aware of a single plugin that handles all of your requirements, but the combination of rufus-scheduler and chronic should get you pretty far.

Rufus scheduler handles scheduling using a cron-like format that also accommodates local timezones. Chronic does the natural language parsing. Putting them together should solve most of your needs.

The Rufus documentation has some notes on pairing the two solutions.

like image 20
Brian Glick Avatar answered Nov 09 '22 11:11

Brian Glick


I recommend taking a look RubyToolbox's Recurring Events gems. It should be up to date and includes ice_cube, mentioned previously.

like image 2
Schneems Avatar answered Nov 09 '22 10:11

Schneems