Каков наилучший способ переопределить поведение Rails ActiveRecord?

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

Кажется, у меня есть как минимум два варианта:

  • Дублировать данные в обязательные модели, эффективно денормализуя мою модель данных, чтобы удаленные записи не влияли на связанные данные.
  • Отмените поведение "уничтожить" ActiveRecord, чтобы сделать что-то вроде установки флага, указывающего, что пользователь "удалил" запись и использует этот флаг, чтобы скрыть запись.

Я пропустил лучший способ?

Вариант 1 кажется мне ужасной идеей, хотя мне бы хотелось услышать аргументы об обратном.

Вариант 2 кажется несколько Rails-ish, но мне интересно, как лучше справиться с этим. Должен ли я создать свой собственный родительский класс, который наследует от ActiveRecord:: Base, переопределить метод destroy там, а затем наследовать от этого класса в моделях, где мне нужно это поведение? Должен ли я также переопределять поведение искателя, поэтому записи, помеченные как удаленные, не возвращаются по умолчанию?

Если бы я сделал это, как бы я работал с динамическими искателями? Как насчет названных областей?

Ответ 1

Если вам действительно не интересно видеть эти записи снова, но только заботитесь о том, чтобы дети все еще существовали, когда родительский объект был уничтожен, задание прост: добавьте :dependent => :nullify в вызов has_many, чтобы установить ссылки на родительский элемент NULL автоматически после уничтожения, и научите рассматривать дело с отсутствием этой ссылки. Однако это работает только в том случае, если вы в порядке, никогда не увидев строку снова, то есть просмотр этих транзакций показывает "[NO LONGER EXISTS]" под названием компании.

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

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

Оттуда, когда вы хотите перечислить эти записи и включить только видимые записи, одно простое решение состоит в том, чтобы включить область visible, которая не включает скрытые записи, и не включать ее, когда вы хотите найти эту конкретную, снова скрытая запись. Другой путь - использовать default_scope для скрытия скрытых записей и использовать Model.with_exclusive_scope { find(id) }, чтобы вытащить скрытую запись, но я бы рекомендовал против нее, так как это может быть серьезная проблема для входящего разработчика и принципиально изменит то, что Model.all возвращает, чтобы совсем не отражать то, что предлагает вызов метода.

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

Ответ 2

Я написал плагин для этой цели, называемый паранойей. Я "заимствовал" идею из acts_as_paranoid и в основном переписывал AAP, используя гораздо меньше кода.

Когда вы вызываете destroy в записи, он фактически не удаляет его. Вместо этого он установит столбец deleted_at в вашей базе данных на текущее время.

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