Моделирование бесконечно повторяющихся задач в расписании (приложение, напоминающее календарь)

Это был довольно камнем преткновения. Предупреждение: следующее не вопрос, скорее объяснение того, что я придумал. Мой вопрос: у вас есть лучший способ сделать это? Есть ли какая-то общая техника для этого, с которой я не знаком? Похоже, это тривиальная проблема.

Итак, у вас есть модель задачи. Вы можете создавать задания, выполнять их, уничтожать. Затем у вас есть повторяющиеся задачи. Это как обычная задача, но в ней есть правило повторения. Однако задачи могут повторяться бесконечно - вы можете пойти на год вперед в расписании, и вы увидите, что задача отображается.

Итак, когда пользователь создает повторяющуюся задачу, вы не хотите создавать тысячи задач в течение ста лет в будущем и сохранять их в базе данных, не так ли? Поэтому я начал думать - как вы их создаете?

Один из способов - создать их при просмотре расписания. Таким образом, когда пользователь движется на месяц вперед, будут созданы любые повторяющиеся задачи. Конечно, это означает, что вы не можете больше работать с записями баз данных. Каждая операция SELECT для задач, которые вы когда-либо делаете, должна быть в контексте определенного диапазона дат, чтобы повторять повторяющиеся задачи в этом диапазоне дат. Это бремя обслуживания и производительности, но выполнимо.

Хорошо, но как насчет оригинальной задачи? Каждая повторяющаяся задача связана с правилом повторения, которое его создало, и каждое правило повторения должно знать исходную задачу, которая начала повторение. Последнее важно, потому что вам нужно клонировать исходную задачу в новые даты, когда пользователь просматривает свое расписание. Я думаю, тоже можно сделать.

Но что произойдет, если исходная задача будет обновлена? Это означает, что теперь, когда мы просматриваем расписание, мы будем создавать повторяющиеся задачи, клонированные от измененной задачи. Это нежелательно. Все неявно сохраняющиеся повторяющиеся задачи должны показать, как выглядела оригинальная задача при добавлении повторения. Поэтому нам нужно сохранить копию оригинальной задачи отдельно и клонировать ее, чтобы повторить работу.

Однако, когда пользователь выполняет задачи в расписании, как нам узнать, нужно ли в какой-то момент создать новую задачу повторения? Мы спрашиваем правило повторения: "эй, нужно ли мне упорствовать на этот день?" и он говорит "да" или "нет". Если для этого повторения уже есть задание на этот день, мы его не создаем. Все хорошо, за исключением того, что пользователь также сможет просто удалить одну из повторяющихся задач, которые были автоматически сохранены. В этом случае, следуя нашей логике, система будет воссоздавать задачу, которая была удалена. Нехорошо. Это означает, что нам нужно сохранить задачу, но пометить ее как удаленную задачу для этого повторения. Мех.

Как я уже сказал вначале, я хочу знать, может ли кто-то другой решить эту проблему и может предоставить здесь архитектурный совет. Должно быть, это грязно? Есть ли что-то более элегантное, что мне не хватает?

Обновление. Поскольку этот вопрос трудно ответить в полной мере, я одобрю наиболее полезное представление о дизайне/архитектуре, у которого есть лучший коэффициент полезности/компромиссов для этого типа проблем. Он не должен охватывать все детали.

Ответ 1

Я знаю, что это старый вопрос, но я только начинаю изучать это для своего собственного приложения, и я нашел эту статью Мартина Фаулера, освещающего: Повторяющиеся события для календарей

Основной вывод для меня заключался в использовании того, что он называет "временными выражениями", чтобы выяснить, попадает ли заказ на определенный диапазон дат вместо того, чтобы вставлять в базу данных бесконечное количество событий (или в ваших делах).

Практически для вашего варианта использования это может означать, что вы храните Задачу с помощью свойства "временного выражения", которое называется 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

Ледяной куб кажется действительно гибким и даже позволяет вам указывать исключения из правил повторения. (Скажем, вы хотите удалить только одно возникновение задачи, но не все из них.)

Проблема заключается в том, что вы не можете запросить базу данных для задачи, которая относится к определенному диапазону дат, поскольку вы только сохранили правило для выполнения задач, а не сами задачи. В моем случае я думаю о добавлении свойства типа next_recurrence_date, которое будет использоваться для выполнения базовой сортировки/фильтрации. Вы могли бы даже использовать это, чтобы бросить задачу в очереди, чтобы что-то сделать в следующую повторяющуюся дату. (Как проверить, прошла ли эта дата, а затем регенерировать ее. Вы даже можете сохранить "архивированную" версию задачи после ее следующей повторяющейся даты.)

Это устраняет проблему: "Что делать, если задача обновляется", поскольку задачи не сохраняются до тех пор, пока они не будут в прошлом.

Во всяком случае, я надеюсь, что это полезно для тех, кто пытается подумать об этом для своего собственного приложения.

Ответ 2

Сделав календарный компонент для внутреннего приложения для социальных сетей, здесь мой подход к этой проблеме.

Крошечный бит фона: мне нужно было забронировать залы заседаний для встреч для всей компании. Каждый зал заседаний должен был быть забронирован как одноразовый, так и на регулярной основе. Как вы узнали, это повторяющиеся правила, которые вас убивают. Дополнительный поворот к моей проблеме состоял в том, что могут быть конфликты, т.е. Два человека могут попытаться забронировать один и тот же зала заседаний в одни и те же дату и время.

Я разделил свои модели на Зал заседаний (очевидно) и Событие (это бронирование, связанное с пользователем). Я думаю, что была и модель объединения, но это было какое-то время. Когда пользователь попытается забронировать зал заседаний, это будет выполнен:

  • Попытка забронировать в первую доступную дату (через пользовательский интерфейс календаря пользователем, похожим на то, как календарь Google создает события)
  • Если это одноразовый, вы закончили
  • Если это повторяющееся событие, постарайтесь немедленно занести следующие 6 событий на основании данного правила (еженедельно, раз в две недели, ежемесячно); Если это не удается, из-за конфликта, закажите те, которые вы можете, отправьте по электронной почте конфликты пользователю.
  • Книга на следующий год или до даты, когда повторение заканчивается фоновым заданием; Следуйте правилу разрешения конфликтов С# 3

При разрешении конфликтов пользователь имел возможность либо разрешать их в каждом конкретном случае, либо перемещать оставшиеся заказы в новую доступную дату и время.

Если пользователь обновил первоначальное бронирование (например, изменил время и дату), у него была возможность обновить только тот или иной следующий повтор. Если последний был выбран, шаги 3 и 4 повторно вызываются после удаления существующих событий.

Если это очень похоже на Календарь Google, вы полностью поняли мой подход:)

Надеюсь, что это поможет.

Ответ 3

Я лично считаю, что (в питоне, который я знаю хорошо) и ruby ​​(который я знаю менее хорошо, но это динамический язык, и поэтому я думаю, что карта концепций 1:1), вы должны использовать генераторы. Как это для минималистического ответа? Теперь, когда вы создаете свой пользовательский интерфейс, вы передаете ссылку на генератор и генерируете нужные вам объекты по мере их запроса.

В качестве интерфейса он имеет следующий элемент и предыдущие методы элементов и действует немного как курсор, который может продвигаться вперед и назад через различные взаимодействия. Это, по сути, фрагмент кода, маскирующийся как бесконечный ряд (массив) без использования бесконечной памяти.

Зачем вам нужно размножать объекты? Что вам действительно нужно - это элементы управления отображением виртуальных данных (для Интернета или рабочего стола), также известные как "пейджинг", я думаю, в веб-контекстах, и вы можете думать о своем расписании как о бесконечной сгенерированной по требованию электронной таблице, без верхней строки, и ни одна нижняя строка. Единственные значения, которые вы должны уметь вычислять (вычислять, а не хранить), - это те, которые появляются прямо сейчас, как видимые пользователю.