# Hiccup [![Build Status](https://travis-ci.org/boblail/hiccup.png?branch=master)](https://travis-ci.org/boblail/hiccup) [![Code Climate](https://codeclimate.com/github/boblail/hiccup.png)](https://codeclimate.com/github/boblail/hiccup) Hiccup mixes a-la-cart recurrence features into your recurring model. It doesn't dictate the data structure of your model, just the interface. It works like Devise does for authenticatable models. Hiccup does provide a lightweight `Schedule` class that mixes in all of Hiccup's feature, but you don't have to use Hiccup's Schedule if you don't want to. ### Usage ```ruby class Schedule extend Hiccup hiccup :enumerable, :validatable, :humanizable, :inferable, :serializable => [:ical] end ``` ### Interface Hiccup requires that a recurring object expose the following properties - **kind**: one of `:never`, `:weekly`, `:monthly`, `:annually` - **start_date**: the date when the recurrence should start - **ends**: either `true` or `false`, indicating whether the recurrence has an end date - **end_date**: the date when the recurrence should end - **skip**: the number of instances to skip (You'd set `skip` to 2 to specify an event that occurs every _other_ week, for example) - **weekly_pattern**: an array of weekdays on which a weekly event should recur - **monthly_pattern**: an array of recurrence rules for a monthly event ### Examples ```ruby # Every other Monday Schedule.new(:kind => :weekly, :weekly_pattern => ["Monday"]) # Every year on June 21 (starting in 1999) Schedule.new(:kind => :yearly, :start_date => Date.new(1999, 6, 21)) # The second and fourth Sundays of the month Schedule.new(:kind => :monthly, :monthly_pattern => [[2, "Sunday"], [4, "Sunday"]]) ``` # Modules ### Enumerable Supplies methods for creating instances of a recurring pattern Examples ```ruby schedule = Schedule.new( :kind => :weekly, :weekly_pattern => %w{Monday Wednesday Friday}, :start_date => Date.new(2009, 3, 15)) # includes? schedule.includes? Date.new(2009, 5, 20) # => true schedule.includes? Date.new(2009, 3, 15) # => false (3/15/09 is a Sunday) # occurrences_between schedule.occurrences_between( Date.new(2009, 3, 26), Date.new(2009, 3, 31)) #=> [Fri, 27 Mar 2009, Mon, 30 Mar 2009] # n_occurrences_before schedule.n_occurrences_before(3, Date.new(2009, 3, 31)) # => [Mon, 30 Mar 2009, Fri, 27 Mar 2009, Wed, 25 Mar 2009] ``` ### Validatable Mixes in ActiveModel validations for recurrence models ### Humanizable Represents a recurring pattern in a human-readable string Examples: ```ruby # A monthly recurrence schedule = Schedule.new(:kind => :monthly, :monthly_pattern => [[-1, "Tuesday"]]) schedule.humanize # => "The last Tuesday of every month" # With skips schedule = Schedule.new(:kind => :weekly, :weekly_pattern => %w[Sunday], :skip => 2) schedule.humanize # => "Every other Sunday" # An anniversary schedule = Schedule.new(:kind => :annually, start_date: Date.new(2012, 10, 1)) schedule.humanize # => "Every year on October 1" ``` ### Inferable Infers a schedule from an array of dates Examples: ```ruby schedule = Schedule.infer %w{2012-7-9 2012-8-13 2012-9-10} schedule.humanize # => "The second Monday of every month" schedule = Schedule.infer %w{2010-3-4 2011-3-4 2012-3-4} schedule.humanize # => "Every year on March 4" schedule = Schedule.infer %w{2012-3-6 2012-3-8 2012-3-15 2012-3-20 2012-3-27 2012-3-29} schedule.humanize # => "Every Tuesday and Thursday" ``` ### Serializable Supports serializing and deserializing a recurrence pattern to an array of formats Examples: ```ruby schedule = Schedule.from_ical <<-ICAL DTSTART;VALUE=DATE-TIME:20090101T000000Z RRULE:FREQ=WEEKLY;INTERVAL=2;BYDAY=TU,TH ICAL schedule.humanize # => "Tuesday and Thursday of every other week" schedule = Schedule.new( :kind => :weekly, :weekly_pattern => %w{Tuesday Thursday}, :start_date => DateTime.new(2009, 1, 1), :skip => 2) schedule.to_ical # => "DTSTART;VALUE=DATE-TIME:20090101T000000Z\nRRULE:FREQ=WEEKLY;INTERVAL=2;BYDAY=TU,TH\n" ``` ## License Copyright (c) 2012 Bob Lail, released under the MIT license