日历中的重复事件 - Rails

30

我正在寻找最佳的递归事件建模方式。我正在使用fullcalendar来显示事件,但我认为最好在rails后端处理递归事件。

我已经查看了其他问题和现有的示例代码,但我没有找到任何符合要求的东西。

它应该像 Google 日历一样工作。因此,应该可以删除/修改递归事件系列的单个事件。但是,在数据库中保存所有事件似乎效率低下。同时还应该有可能创建没有任何重复的单个事件。

什么样的模型架构会比较好呢?

我的事件模型目前看起来像这样(没有其他属性):

# Table name: events
#
#  id              :integer         not null, primary key
#  employee_id     :integer
#  created_at      :datetime
#  updated_at      :datetime
#  starts_at       :datetime
#  ends_at         :datetime
#

class Event < ActiveRecord::Base
  attr_accessible :starts_at, :ends_at
end
4个回答

44

以下是我的建模方法。我对Google日历的使用不多,因此我基于iCal的重复事件功能来设计这个模型。

所有模型都应该有常见的id、created_at和updated_at属性。下面是自定义属性列表。如果属性是另一个模型,则需要使用诸如has_onebelongs_to的关联方式来实现。

  • RecurrencePeriod
    • Event base_event # has_one :base_event, :class_name'Event'
    • Time end_date # 可能为nil,如果永久重复
    • WeeklyRecurrence recurrence # has_one :recurrence, :as=>:recurrence
    • Array[OccurrenceOverride] overrides # has_many :overrides, :class_name=>'OccurrenceOverride'

RecurrencePeriod从其base_event开始的日期开始。同时,我假设Eventemployee_id指的是创建该事件的员工。一个RecurrencePeriod也将属于创建base_event的员工。

该模型取决于您希望指定重复次数的灵活性。您是要支持“每两周的周二和周四上午10点到11点,下午2点到3点”还是只支持“每周重复”?这里的模型仅支持“每周重复”,“每两周重复”等;如果需要,可以进行扩展。

  • WeeklyRecurrence
    • Integer weeks_between_recurrences
    • RecurrencePeriod recurrence_period # belongs_to :recurrence, :polymorphic=>true

我在这里使用多态关联,因为如果您想要多个类型的重复,如WeeklyRecurrenceDailyRecurrence,它们可能很有用。但我不确定这是否是正确的建模方式,所以如果它们不是,只需改用has_one:weekly_recurrencebelongs_to:recurrence_period

Ice cube库似乎可以用于计算重复。如果上面的WeeklyRecurrence不够强大,您可能只需在模型中存储一个Ice Cube Schedule对象,取代WeeklyRecurrence 。要将Schedule对象存储在模型中,请将其保存为属性"schedule",将serialize: schedule放在模型定义中,并在数据库中生成文本列"schedule"。

OccurrenceOverride处理编辑正在重复的事件的单个实例的情况。

  • OccurrenceOverride
    • RecurrencePeriod recurrence_period_to_override # belongs_to :recurrence_period_to_override, :class_name=>'RecurrencePeriod'
    • Time original_start_time # 唯一标识要替换的那个RecurrencePeriod内的重复
    • Event replacement_event # has_one :replacement_event, :class_name=>'Event'; 如果删除了该重复,则可能为nil

不必单独存储每个事件的出现,只需在需要在视图中显示它们时临时生成。在RecurrencePeriod中创建一个方法generate_events_in_range(start_date, end_date),生成Event并不保存到数据库,仅用于传递给视图以便展示。

当用户编辑重复事件时,应该有选择修改所有事件、所有未来事件或仅该事件的选项。如果他们修改了所有事件,则修改RecurrencePeriod的基础事件。如果他们修改所有未来事件,则使用您应该在RecurrencePeriod上实现的一种方法将其拆分为两个在某个日期之前和之后的RecurrencePeriod,然后仅保存更改到第二个期间。如果他们仅修改该事件,则为他们要覆盖的时间创建一个OccurrenceOverride,并将更改保存到覆盖的替换事件中。

当用户表示某个事件现在应该每两周重复一次,可以创建一个新的RecurrencePeriod,该事件作为基础事件,无结束日期。其重复应为新的WeeklyRecurrence,其中weeks_between_recurrence=2,并且不应该有任何OccurrenceOverride


1
有没有经验遇到过未持久化出现的问题,因为无法通过ID访问它?(性能、复杂性等) - ted
@ted 很好的问题。我从未实现过这个功能,所以我不知道按需生成事件是否会导致任何问题。如果必要的话,我想你可以为生成的事件设置一个缓存。缓存系统可能会利用生成的事件需要保存的事实,这与 OccurrenceOverride 在概念上非常相似,尽管并不完全相同。 - Rory O'Kane

9
在我的情况下,我做了类似于这样的事情:
# Holds most of my event's data; name, description, price ...
class Event < ActiveRecord::Base
  has_many :schedules
  has_many :occurrences
  attr_accessible :started_at, :expired_at # expired_at is optional
end

# Holds my schedule object
class Schedule < ActiveRecord::Base
  belongs_to :event
  attr_accessible :ice_cube_rule # which returns my deserialized ice_cube object
end

# Holds generated or manually created event occurrences 
class Occurrence < ActiveRecord::Base
  belongs_to :event
  attr_accessible :started_at, :expired_at
  attr_accessible :generated # helps me tell which occurrences are out of an ice_cube generated serie
  attr_accessible :canceled_at
end

从那里开始,我使用ice_cube来管理事件发生的计算,并将结果存储在occurrences表中。我最初尝试不使用Occurrence模型,但无论规则引擎多么先进,总会有例外情况,因此将出现次数存储在它们自己的模型中可以给您带来灵活性。
拥有一个Occurrence模型可以使在日历上显示事件或使用日期搜索过滤器变得更加容易,因为您只需要查询出现次数,然后显示相关事件的数据,而不是收集给定日期范围内的所有事件,然后再过滤掉日程表不匹配的事件。
此外,您可以将事件出现标记为已取消或修改(将生成的属性设置为false,以便在编辑ice_cube日程表时不会被清除...或者根据您的业务需求进行调整)。
当然,如果您有无限重复的事件,您将希望限制未来的生成次数,并使用自动化的rake任务清理旧的事件并为未来一年生成出现次数。
到目前为止,这种模式对我非常有效。
此外,请查看recurring_select gem,这是一个相当不错的ice_cube表单输入。

2
不错的文章。想知道您对这种方法的看法:http://blog.plataformatec.com.br/2010/04/recurring-events 看起来与您的类似。 - Bruno
Bruno,你提到的方法是有效的,只要你不需要在这些重复事件中保持状态或处理异常(例如:在下雨的情况下将音乐会推迟到第二天)。 - Jim
@Jim,你有演示应用程序或示例应用程序可以让我玩一下吗?我喜欢你的解决方案。 - Frank004
@Frank004 不好意思,但是除了这个片段以外,没有太多内容。其他的都是你自己的业务逻辑。我在一年多前实现了这个技术,它仍然有效。 - Jim
@Jim 无论如何,谢谢你 :) - Frank004

6
仅是我的个人意见,也许评论者会指出我目前没有考虑到的问题:
我会创建一个RecurringEvent模型(或任何你想称呼它的名称),该模型具有许多事件。
假设每个事件都是由员工创建的(根据您的笔记),那么RecurringEvent也将属于employee。您可以构建一个has_many:through关系,其中员工拥有许多事件和许多重复事件。
RecurringEvent模型可以有一个开始日期和一个模式,并且最初可以使用此模式创建单个发生事件。然后,在任何作为重复系列一部分的事件上,您都可以修改或删除该单个事件,但您还可以“重新生成系列”,删除系列中的所有事件(或系列中的所有未来事件)并基于新模式重建它们,例如将会议从“每周二”移动到“每周四”。
这种方法的另一个好处是,您可以创建一个一目了然的重复事件列表,这可能会为您提供有关人们主要义务的有用洞察力。
就像我说的,这只是我的想法,我没有构建过类似的东西,所以我不知道我建议的方法中是否存在任何大的陷阱。
祝你好运,请发布你最终做什么!

-1

我对Rails还比较新,你的解决方案听起来很有趣。为了创建日程表和相关事件,您是否在Event模型中使用条件回调?

在我的情况下,用户将能够创建每周重复或非重复事件。因此,我考虑在事件模型中添加一个重复布尔字段。所以我想您会有第一个回调来创建日程表:

before_save :create_weekly_schedule, if: :recurring

并基本上还有第二个用于创建出现次数:

after_save :create_occurences_if_recurring

def create_occurences_if_recurring
  schedules.each do |sched|
    occurences.create(start_date: sched.start_time, end_date: sched.end_time)
  end
end

你的解决方案听起来合理吗? 谢谢


1
我看到你在回复我的答案。当你回复特定的答案时,应该在那个答案下发表评论,否则作者将不会收到通知。此外,如果你的问题很长(像这个),不要把它放在原始问题的另一个答案中;在网站上创建一个新问题,链接到你正在回复的答案。 - Rory O'Kane
关于你的解决方案,我认为存在一个问题。你使用了schedules.each,但是如果日程安排只是“每周重复”,那么each循环将永远持续下去,因为日程安排没有指定结束日期。这就是为什么我没有提前生成所有事件的原因——因为它们有无限多个。 - Rory O'Kane

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接