Rails: удалить каскад против зависимого уничтожения

Предполагая, что у меня две таблицы: users и orders. У пользователя много заказов, поэтому, естественно, в моей таблице заказов есть внешний user_id.

Какова наилучшая практика в рельсах (с точки зрения скорости, стиля и ссылочной целостности), чтобы гарантировать, что если пользователь будет удален, все зависимые заказы также будут удалены? Я рассматриваю следующие варианты:

Случай 1. Используя :dependent => :destroy в пользовательской модели

Случай 2. Определение табличных заказов в postgres и записи

user_id integer REFERENCES users(id) ON DELETE CASCADE

Есть ли причина, почему я должен использовать Случай 1? Кажется, что Case 2 делает все, что я хочу, чтобы он делал? Есть ли разница в скорости выполнения?

Ответ 1

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

В раннем детстве приложения я бы рекомендовал перейти с :dependent => :destroy, потому что он позволяет вам развиваться таким образом, который не зависит от базы данных. После того, как вы начнете масштабироваться, вы должны начать делать это в базе данных по причинам производительности/целостности.

Ответ 2

has_many :orders, dependent: :destroy

  • Самый безопасный вариант для автоматического сохранения целостности данных.
  • У вас есть полиморфные ассоциации, и вы не хотите использовать триггеры.


add_foreign_key :orders, :users, on_delete: :cascade (при миграции базы данных)

  • Вы не используете полиморфные ассоциации или хотите использовать триггеры для каждой полиморфной ассоциации.


has_many :orders, dependent: :delete_all

  • Использовать только тогда, когда has_many является листом node в дереве ассоциации (т.е. у ребенка нет другой ассоциации has_many с ссылками на внешние ключи).

Ответ 3

Я бы использовал вариант 1. Хотя он может работать, я вижу ряд проблем с опцией 2:

  • ActiveRecord не будет знать, что эти записи были удалены, что может привести к нестабильному поведению
  • было бы непонятно, кто-нибудь, кто читает код, который удаляет пользователя, означает, что все их заказы также будут удалены.
  • обработчики destroy при заказе не будут запускать

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

Другой вариант - использовать :dependent => :delete_all. Это будет быстрее, чем :dependent => :destroy и избежать недостатков 1 и 2 выше. Подробнее см. здесь.