对时间表中无限重复的任务进行建模(类似日历的 Rails 应用程序)
这一直是一个很大的绊脚石。警告:以下不是问题,而是对我的想法的解释。我的问题是——你有更好的方法吗?是否有一些我不熟悉的常用技术?看来这是一个微不足道的问题。
所以你有任务模型。您可以创建任务、完成任务、销毁任务。然后你就有重复的任务。它就像常规任务一样,但附加了重复规则。但是,任务可以无限期地重复 - 您可以将计划提前一年,并且您应该看到任务显示。
因此,当用户创建重复任务时,您不想在未来数百年内构建数千个任务,并将它们保存到数据库中,对吧?所以我开始思考——如何创建它们?
一种方法是在您查看日程安排时创建它们。因此,当用户提前一个月移动时,将创建任何重复任务。当然,这意味着您不能再简单地处理任务的数据库记录。您对任务执行的每个 SELECT 操作都必须在特定日期范围的上下文中,以便触发该日期范围内的重复任务持续存在。这是维护和性能的负担,但也是可行的。
好吧,但是原来的任务呢?每个重复任务都与创建它的重复规则相关联,并且每个重复规则都需要知道启动重复的原始任务。后者很重要,因为您需要在用户浏览其日程安排时将原始任务克隆到新日期。我想也是可行的。
但是如果原始任务更新了会发生什么?这意味着现在当我们浏览计划时,我们将创建从修改后的任务克隆的重复任务。这是不可取的。所有隐式保留的重复任务都应显示为添加重复任务时原始任务的样子。因此,我们需要单独存储原始任务的副本,并从中克隆,以便重复工作。
但是,当用户导航计划中的任务时,我们如何知道在特定点是否需要创建新的重复任务?我们问重复规则:“嘿,我应该坚持这一天的任务吗?”它说是或否。如果这一天已经有一个重复任务,我们就不会创建任务。一切都很好,除了用户还应该能够简单地删除已自动保留的重复任务之一。在这种情况下,按照我们的逻辑,系统将重新创建已删除的任务。不好。因此,这意味着我们需要继续存储该任务,但将其标记为本次重复的已删除任务。嗯。
正如我在一开始所说的,我想知道是否有人解决了这个问题并且可以在这里提供架构建议。有必要这么乱吗?我还缺少什么更优雅的东西吗?
更新:由于这个问题很难完美回答,所以我会批准对设计/架构最有帮助的见解,它对此类问题具有最佳的帮助/权衡比。它不必包含所有细节。
This has been quite a stumbling block. Warning: the following is not a question, rather explanation of what I came up with. My question is — do you have a better way to do this? Is there some common technique for this that I'm not familiar with? Seems like this is a trivial problem.
So you have Task model. You can create tasks, complete them, destroy them. Then you have recurring tasks. It's just like regular task, but it has a recurrence rule attached to it. However, tasks can recur indefinitely — you can go a year ahead in the schedule, and you should see the task show up.
So when a user creates a recurring task, you don't want to build thousands of tasks for hundred years into the future, and save them to database, right? So I started thinking — how do you create them?
One way would be to create them as you view your schedule. So, when the user is moving a month ahead, any recurring tasks will be created. Of course that means that you can't simply work with database records of tasks any longer. Every SELECT operation on tasks you ever do has to be in the context of a particular date range, in order to trigger recurring tasks in that date range to persist. This is a maintenance and performance burden, but doable.
Alright, but how about the original task? Every recurrent task gets associated with the recurrence rule that created it, and every recurrence rule needs to know the original task that started the recurrence. The latter is important, because you need to clone the original task into new dates as the user browses their schedule. I guess doable too.
But what happens if the original task is updated? It means that now as we browse the schedule, we will be creating recurring tasks cloned off of the modified task. That's undesirable. All the implicitly persisted recurring tasks should show up the way the original task looked like when recurrence was added. So we need to store a copy of the original task separately, and clone from that, in order for recurrence to work.
However, when the user navigates the tasks in the schedule, how do we know if at a particular point a new recurrence task needs to be created? We ask recurrence rule: "hey, should I persist a task for this day?" and it says yes or no. If there is already a task for this recurrence for this day, we don't create one. All nice, except a user shall also be able to simply delete one of the recurring tasks that has been automatically persisted. In that case following our logic, the system will re-create the task that has been deleted. Not good. So it means we need to keep storing the task, but mark it as deleted task for this recurrence. Meh.
As I said in the beginning, I want to know if somebody else tackled this problem and can provide architectural advice here. Does it have to be this messy? Is there anything more elegant I'm missing?
Update: Since this question is hard to answer perfectly, I will approve the most helpful insight into design/architecture, which has the best helpfulness/trade-offs ratio for this type of problem. It does not have to encompass all the details.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
我知道这是一个老问题,但我刚刚开始为自己的应用程序研究这个问题,我发现 Martin Fowler 的这篇论文很有启发性: 日历的重复事件
对我来说,主要的收获是使用他所谓的“时间表达式”来确定预订是否在某个日期范围内,而不是尝试插入无限数量的事件(或者在您的情况下)任务)存入数据库。
实际上,对于您的用例,这可能意味着您使用名为
schedule
的“时间表达式”属性来存储任务。 ice_cube 循环 gem 能够将自身序列化为活动记录属性像这样:Icecube 看起来非常灵活,甚至允许您指定重复规则的例外情况。 (假设您只想删除该任务的一次出现,而不是全部。)
问题是您无法真正在数据库中查询属于特定日期范围内的任务,因为您只存储了制定任务的规则,而不是任务本身。对于我的情况,我正在考虑添加一个像“next_recurrence_date”这样的属性,它将用于进行一些基本的排序/过滤。您甚至可以使用它将任务放入队列中,以便在下一个重复日期完成某些任务。 (比如检查该日期是否已过,然后重新生成它。您甚至可以在下一个重复日期过去后存储任务的“存档”版本。)
这解决了您的问题“如果任务更新了怎么办”,因为任务不是“我从来没有坚持过,直到它们成为过去。
不管怎样,我希望这对那些试图为自己的应用程序思考这个问题的人有所帮助。
I know this is an old question but I'm just starting to look into this for my own application and I found this paper by Martin Fowler illuminating: Recurring Events for Calendars
The main takeaway for me was using what he calls "temporal expressions" to figure out if a booking falls on a certain date range instead of trying to insert an infinite number of events (or in your case tasks) into the database.
Practically, for your use case, this might mean that you store the Task with a "temporal expression" property called
schedule
. The ice_cube recurrence gem has the ability to serialize itself into an active record property like so:Ice cube seems really flexible and even allows you to specify exceptions to the recurrence rules. (Say you want to delete just one occurrence of the task, but not all of them.)
The problem is that you can't really query the database for a task that falls in a specific range of dates, because you've only stored the rule for making tasks, not the tasks themselves. For my case, I'm thinking about adding a property like "next_recurrence_date" which will be used to do some basic sorting/filtering. You could even use that to throw a task on a queue to have something done on the next recurring date. (Like check if that date has passed and then regenerate it. You could even store an "archived" version of the task once its next recurring date passes.)
This fixes your issue with "what if the task is updated" since tasks aren't ever persisted until they're in the past.
Anyway, I hope that is helpful to someone trying to think this through for their own app.
为内部社交网络应用程序完成了类似日历的组件后,这是我解决该问题的方法。
一点背景知识:我需要为整个公司预订董事会会议室。每个董事会会议室都需要一次性或定期预订。正如您所发现的,是重复规则杀死了您。我的问题的另一个问题是可能会发生冲突,即两个人可能会尝试在同一日期和时间预订同一会议室。
我将模型分为会议室(显然)和活动(这是与用户关联的预订)。我认为也有一个加入模型,但已经有一段时间了。当用户尝试预订会议室时,所采取的过程如下:
解决冲突时,用户可以选择根据具体情况解决冲突,或将剩余预订移至新的可用日期和时间。
如果用户更新了原始预订(例如更改了时间和日期),他/她可以选择仅更新该预订或每个后续重复预订。如果选择后者,则在删除现有事件后重新调用步骤 3 和 4。
如果这听起来很像 Google 日历,那么您已经完全理解了我的方法,:)
希望这会有所帮助。
Having done a calendar-like component for an internal social networking app, here's my approach to that problem.
Tiny bit of background: I needed to book boardrooms for meetings for the entire company. Every boardroom needed to be booked either as a one-off or on a recurring basis. As you've found out, it's the recurrence rules that kill you. The additional twist to my problem was that there could be conflicts, i.e. two people could try to book the same boardroom for the same date and time.
I split my models into Boardroom (obviously) and Event (which is the booking associated to a User). I think there was a join model, as well, but it's been a while. When a User would try to book a boardroom, this is the process taken:
When resolving the conflicts, the user had the option of either resolving them on a case-by-case basis or moving the remaining bookings to the new, available date and time.
If the user updated the original booking (e.g changed the time and date), he/she had the option of updating only the that one or every following recurrence. If the latter was selected, steps 3 and 4 are re-invoked after the deletion of existing events.
If this sounds a lot like Google Calendar, then you've fully understood my approach, :)
Hope this helps.
我个人认为(在我熟悉的Python中)和Ruby(我不太了解,但它是一种动态语言,所以我认为概念图1:1),你应该使用生成器。简约的答案怎么样?现在,当您生成 UI 时,您可以传入对生成器的引用,它会根据请求生成您需要的对象。
作为一个接口,它具有下一项和上一项方法,并且行为有点像光标,可以在各种交互中向前和向后移动。事实上,它是一段伪装成无限系列(数组)的代码,而不使用无限内存。
为什么需要增殖对象?您真正需要的是虚拟数据显示控件(用于网络或桌面),我认为在网络环境中也称为“分页”,您可以将您的日程安排视为无限的按需生成的电子表格,没有顶行,并且没有底行。您需要能够计算(计算,而不是存储)的唯一值是当前显示的值,对用户可见。
I personally think that (in python which I know well), and ruby (which I know less well, but it's a dynamic language, and so I think the concepts map 1:1), you should be using generators. How's that for a minimalistic answer? Now, when you generate your UI, you pass in a reference to the generator, and it generates the objects you need, as they are requested.
As an interface, it has next item, and previous item methods, and acts a bit like a cursor that can wade forward and backward through the various interations. It is in fact, a piece of code masquerading as an infinite series (array) without using infinite memory.
Why do you need to proliferate objects? What you really need are virtual data display controls (for the web or desktop) also known as "paging" I think, in web contexts, and you can think of your schedule as an infinite generated-on-demand spreadsheet, with no top row, and no bottom row. The only values you need to be able to calculate (calculate, not store) are the ones that appear right now, as visible to the user.