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 выше. Подробнее см. здесь.