class RecurringTodo < ApplicationRecord belongs_to :context belongs_to :project belongs_to :user has_many :todos scope :active, -> { where state: 'active' } scope :completed, -> { where state: 'completed' } include IsTaggable include AASM aasm :column => :state do state :active, :initial => true, :before_enter => Proc.new { self.occurrences_count = 0 } state :completed, :before_enter => Proc.new { self.completed_at = Time.zone.now }, :before_exit => Proc.new { self.completed_at = nil } event :complete do transitions :to => :completed, :from => [:active] end event :activate do transitions :to => :active, :from => [:completed] end end validates :description, presence: true, length: { maximum: 100 } validates :notes, length: { maximum: 60_000, allow_nil: true } validates :recurring_period, presence: true validates :target, presence: true validates :ends_on, presence: true validates :context, presence: true validate :period_validation validate :pattern_specific_validations def pattern_specific_validations if pattern pattern.validate else errors[:recurring_todo] << "Invalid recurrence period '#{recurring_period}'" end end def valid_period? %W[daily weekly monthly yearly].include?(recurring_period) end def period_validation errors.add(:recurring_period, "is an unknown recurrence pattern: '#{recurring_period}'") unless valid_period? end # the following recurrence patterns can be stored: # # daily todos - recurrence_period = 'daily' # every nth day - nth stored in every_other1 # every work day - only_work_days = true # tracks will choose between both options using only_work_days # weekly todos - recurrence_period = 'weekly' # every nth week on a specific day - # nth stored in every_other1 and the specific day is stored in every_day # monthly todos - recurrence_period = 'monthly' # every day x of nth month - x stored in every_other1 and nth is stored in every_other2 # the xth y-day of every nth month (the forth tuesday of every 2 months) - # x stored in every_other3, y stored in every_count, nth stored in every_other2 # choosing between both options is done on recurrence_selector where 0 is # for first type and 1 for second type # yearly todos - recurrence_period = 'yearly' # every day x of month y - x is stored in every_other1, y is stored in every_other2 # the x-th day y of month z (the forth tuesday of september) - # x is stored in every_other3, y is stored in every_count, z is stored in every_other2 # choosing between both options is done on recurrence_selector where 0 is # for first type and 1 for second type def pattern if valid_period? @pattern = eval("RecurringTodos::#{recurring_period.capitalize}RecurrencePattern.new(user)", binding, __FILE__, __LINE__) @pattern.build_from_recurring_todo(self) end @pattern end def recurrence_pattern pattern.recurrence_pattern end def recurring_target_as_text pattern.recurring_target_as_text end def starred? has_tag?(Todo::STARRED_TAG_NAME) end def get_due_date(previous) pattern.get_due_date(previous) end def get_show_from_date(previous) pattern.get_show_from_date(previous) end def done?(end_date) !continues_recurring?(end_date) end def toggle_completion! completed? ? activate! : complete! end def toggle_star! if starred? _remove_tags(Todo::STARRED_TAG_NAME) else _add_tags(Todo::STARRED_TAG_NAME) end tags.reload starred? end def remove_from_project! self.project = nil save end def clear_todos_association unless todos.nil? todos.each do |t| t.recurring_todo = nil t.save end end end def increment_occurrences self.occurrences_count += 1 save end def continues_recurring?(previous) pattern.continues_recurring?(previous) end end