问题 在计划中建模无限期重复任务(类似日历的rails应用程序)


这是一个相当绊脚石。警告:以下不是问题,而是对我提出的问题的解释。我的问题是 - 你有更好的方法吗?是否有一些我不熟悉的常用技巧?似乎这是一个微不足道的问题。

所以你有任务模型。您可以创建任务,完成任务,销毁它们。然后你有重复的任务。它就像常规任务一样,但它附有一个重复规则。但是,任务可以无限期地重现 - 您可以在计划中提前一年,并且您应该看到任务显示出来。

因此,当用户创建定期任务时,您不希望在未来的一百年内构建数千个任务,并将它们保存到数据库中,对吗?所以我开始思考 - 你是如何创造它们的?

一种方法是在查看日程安排时创建它们。因此,当用户提前一个月移动时,将创建任何重复任务。当然,这意味着您不能再简单地处理任务的数据库记录。您执行的任务上的每个SELECT操作都必须位于特定日期范围的上下文中,以便触发该日期范围内的重复任务。这是一种维护和性能负担,但可行。

好吧,但原来的任务怎么样?每个周期性任务都与创建它的重复规则相关联,并且每个重复规则都需要知道开始重复的原始任务。后者很重要,因为当用户浏览他们的日程安排时,您需要将原始任务克隆到新日期。我猜也是可行的。

但是如果原始任务更新会发生什么?这意味着,当我们浏览计划时,我们将创建从修改的任务克隆的重复任务。那是不可取的。所有隐式持久的重复任务都应该显示添加重复时原始任务的样子。因此,我们需要单独存储原始任务的副本,并从中进行克隆,以便重复工作。

但是,当用户在计划中导航任务时,我们如何知道在特定点是否需要创建新的重复任务?我们问复发规则:“嘿,我今天应该继续执行任务吗?”它说是或否。如果今天已经有这个重复的任务,我们不会创建一个。一切都很好,除了用户还应该能够简单地删除一个自动持久的重复任务。在这种情况下遵循我们的逻辑,系统将重新创建已删除的任务。不好。因此,这意味着我们需要继续存储任务,但将此标记为已删除的任务以便再次发生。咩。

正如我在开始时所说,我想知道其他人是否解决了这个问题并且可以在这里提供建筑建议。它必须是这个混乱吗?有什么比我更缺的优雅吗?

更新:由于这个问题难以完美回答,我将批准对设计/架构最有帮助的见解,这对于此类问题具有最佳的帮助/权衡比率。它不必包含所有细节。


12818
2018-03-18 00:18


起源



答案:


我知道这是一个老问题,但我刚刚开始研究这个问题,我发现Martin Fowler发表的这篇论文说明: 日历的重复事件

我的主要内容是使用他所谓的“时态表达式”来确定预订是否属于某个日期范围而不是尝试将无限数量的事件(或在您的案例任务中)插入数据库。

实际上,对于您的用例,这可能意味着您使用名为“temporal expression”属性存储Task schedule。该 ice_cube复发宝石 能够将自身序列化为活动记录属性 像这样

class Task < ActiveRecord::Base
  include IceCube
  serialize :schedule, Hash

  def schedule=(new_schedule)
    write_attribute(:schedule, new_schedule.to_hash)
  end

  def schedule
    Schedule.from_hash(read_attribute(:schedule))
  end
end

Ice cube看起来非常灵活,甚至允许您指定重复规则的例外。 (假设您只想删除一次任务,但不是全部。)

问题是您无法真正在数据库中查询属于特定日期范围的任务,因为您只存储了创建任务的规则,而不是任务本身。对于我的情况,我正在考虑添加一个像“next_recurrence_date”这样的属性,用于做一些基本的排序/过滤。您甚至可以使用它将任务放在队列中,以便在下一个重复日期完成某些操作。 (比如检查该日期是否已经过去,然后重新生成它。您甚至可以在下一个重复日期过后存储任务的“已归档”版本。)

这可以解决您的问题“如果任务已更新会怎样”,因为任务在过去之前不会持续存在。

无论如何,我希望这对有人试图通过他们自己的应用程序进行思考是有帮助的。


8
2017-11-03 04:30





为内部社交网络应用程序完成了类似日历的组件,这是我解决该问题的方法。

一点点背景:我需要为整个公司预订会议室。每个会议室都需要以一次性或定期的方式预订。正如你所发现的,这是一种杀死你的复发规则。我的问题的另一个转折点是可能存在冲突,即两个人可能会尝试在相同的日期和时间预订同一个会议室。

我将模型拆分为Boardroom(显然)和Event(与用户关联的预订)。我认为还有一个连接模型,但它已经有一段时间了。当用户尝试预订会议室时,这是采取的过程:

  1. 尝试在第一个可用日期预订(由用户通过日历用户界面完成,类似于Google日历创建活动的方式)
  2. 如果它是一次性的,你就完成了
  3. 如果这是一个反复出现的事件,请尝试根据给出的规则(每周,每两周,每月)立即预订接下来的6个事件;如果由于冲突而失败,请预订可以通过电子邮件将冲突发送给用户
  4. 预订明年或直到重复在后台工作结束的日期;遵循#3中的冲突解决规则

在解决冲突时,用户可以选择根据具体情况解决冲突,也可以将剩余的预订移至新的可用日期和时间。

如果用户更新了原始预订(例如更改了时间和日期),则他/她可以选择仅更新该一次或每次后续重复。如果选择后者,则在删除现有事件后重新调用步骤3和4。

如果这听起来很像谷歌日历,那么你完全理解我的方法,:)

希望这可以帮助。


3
2018-03-18 01:14



你的建议的关键(适用于这个问题)似乎是在“明年[...]后期工作的书中”。这很有趣。您选择一个任意的合理限制并持续到该限制。这解决了数据库中没有物理可用记录的问题。但是,如果您需要运行某种超出合理限制的计算或报告,那会感觉如何?当前日期接近年底时会发生什么?那么你再创建一年的记录吗?等等。 - Max Chernyak
是的,我设置了两个后台工作。一个是为新创建的事件安排,另一个是每日任务,检查是否需要更多事件。如果没有结束日期的定期事件剩下少于5个事件,我将启动另一批保存。就报告而言,我当时没有这样的要求。基本上,我必须展示的是当前重复活动的最后日期。如果需要,你必须小心不要超过日期,当然。 - Srdjan Pejic


答案:


我知道这是一个老问题,但我刚刚开始研究这个问题,我发现Martin Fowler发表的这篇论文说明: 日历的重复事件

我的主要内容是使用他所谓的“时态表达式”来确定预订是否属于某个日期范围而不是尝试将无限数量的事件(或在您的案例任务中)插入数据库。

实际上,对于您的用例,这可能意味着您使用名为“temporal expression”属性存储Task schedule。该 ice_cube复发宝石 能够将自身序列化为活动记录属性 像这样

class Task < ActiveRecord::Base
  include IceCube
  serialize :schedule, Hash

  def schedule=(new_schedule)
    write_attribute(:schedule, new_schedule.to_hash)
  end

  def schedule
    Schedule.from_hash(read_attribute(:schedule))
  end
end

Ice cube看起来非常灵活,甚至允许您指定重复规则的例外。 (假设您只想删除一次任务,但不是全部。)

问题是您无法真正在数据库中查询属于特定日期范围的任务,因为您只存储了创建任务的规则,而不是任务本身。对于我的情况,我正在考虑添加一个像“next_recurrence_date”这样的属性,用于做一些基本的排序/过滤。您甚至可以使用它将任务放在队列中,以便在下一个重复日期完成某些操作。 (比如检查该日期是否已经过去,然后重新生成它。您甚至可以在下一个重复日期过后存储任务的“已归档”版本。)

这可以解决您的问题“如果任务已更新会怎样”,因为任务在过去之前不会持续存在。

无论如何,我希望这对有人试图通过他们自己的应用程序进行思考是有帮助的。


8
2017-11-03 04:30





为内部社交网络应用程序完成了类似日历的组件,这是我解决该问题的方法。

一点点背景:我需要为整个公司预订会议室。每个会议室都需要以一次性或定期的方式预订。正如你所发现的,这是一种杀死你的复发规则。我的问题的另一个转折点是可能存在冲突,即两个人可能会尝试在相同的日期和时间预订同一个会议室。

我将模型拆分为Boardroom(显然)和Event(与用户关联的预订)。我认为还有一个连接模型,但它已经有一段时间了。当用户尝试预订会议室时,这是采取的过程:

  1. 尝试在第一个可用日期预订(由用户通过日历用户界面完成,类似于Google日历创建活动的方式)
  2. 如果它是一次性的,你就完成了
  3. 如果这是一个反复出现的事件,请尝试根据给出的规则(每周,每两周,每月)立即预订接下来的6个事件;如果由于冲突而失败,请预订可以通过电子邮件将冲突发送给用户
  4. 预订明年或直到重复在后台工作结束的日期;遵循#3中的冲突解决规则

在解决冲突时,用户可以选择根据具体情况解决冲突,也可以将剩余的预订移至新的可用日期和时间。

如果用户更新了原始预订(例如更改了时间和日期),则他/她可以选择仅更新该一次或每次后续重复。如果选择后者,则在删除现有事件后重新调用步骤3和4。

如果这听起来很像谷歌日历,那么你完全理解我的方法,:)

希望这可以帮助。


3
2018-03-18 01:14



你的建议的关键(适用于这个问题)似乎是在“明年[...]后期工作的书中”。这很有趣。您选择一个任意的合理限制并持续到该限制。这解决了数据库中没有物理可用记录的问题。但是,如果您需要运行某种超出合理限制的计算或报告,那会感觉如何?当前日期接近年底时会发生什么?那么你再创建一年的记录吗?等等。 - Max Chernyak
是的,我设置了两个后台工作。一个是为新创建的事件安排,另一个是每日任务,检查是否需要更多事件。如果没有结束日期的定期事件剩下少于5个事件,我将启动另一批保存。就报告而言,我当时没有这样的要求。基本上,我必须展示的是当前重复活动的最后日期。如果需要,你必须小心不要超过日期,当然。 - Srdjan Pejic


我个人认为(在我熟悉的python中)和ruby(我知道的不太好,但它是一种动态语言,所以我认为概念图1:1),你应该使用生成器。这对于简约的回答怎么样?现在,当您生成UI时,您将传递对生成器的引用,并根据请求生成所需的对象。

作为一个接口,它有下一个项目和前面的项目方法,并且有点像一个可以在各种交互中向前和向后跋涉的游标。事实上,它是一段伪装成无限系列(数组)而不使用无限内存的代码。

为什么需要扩散物体?您真正需要的是虚拟数据显示控件(对于Web或桌面),我认为,在网络环境中也称为“分页”,您可以将您的日程安排视为无限生成按需电子表格,没有顶行,并没有底线。您需要能够计算(计算,而不是存储)的唯一值是现在出现的值,对用户可见。


0
2018-03-18 00:49



这几乎与我写的内容一致。缺点是您不能简单地处理任务记录(用于计算,报告等),因为重复记录尚未实际存储。您必须有一些日期范围来控制该范围的重复任务的创建。 - Max Chernyak