Пришла в Ruby из Go полгода назад и ловлю странное. При оформлении заказа клиенту уходит письмо «заказ принят», а заказа в базе нет: транзакция откатилась из-за ошибки при списании остатков. Письмо отправляется из колбэка модели:
class Order < ApplicationRecord
after_create :send_confirmation
private
def send_confirmation
OrderMailer.confirmation(self).deliver_now
end
end
Создание заказа и списание остатков завёрнуты в один transaction. Rails 8.0, MySQL 8.
after_create выполняется внутри транзакции, до коммита. Письмо уже ушло, потом транзакция откатилась, а вернуть письмо нельзя. Нужен after_create_commit: он вызывается только после успешного коммита, а при откате не вызывается совсем.
after_create_commit :send_confirmation
Если потом переведёте письмо на deliver_later, ставьте джобу тоже только после коммита, иначе воркер может взять её раньше, чем заказ станет виден, и упадёт на десериализации. Правило простое: всё, что уходит наружу (письма, вебхуки, запросы к платёжке), только после коммита.
Одна ловушка с этими колбэками. Если повесить after_create_commit и after_update_commit на один и тот же метод, сработает только последний: оба внутри превращаются в after_commit, и второй затирает первый. Пишите after_commit :send_confirmation, on: [:create, :update] или after_save_commit. А когда колбэков после коммита становится больше трёх, я бы вынес оформление заказа в отдельный объект и вызывал письмо явно, читать такое проще.
Тоже на этом спотыкался после Laravel. Там для очереди есть отдельная настройка «после коммита», и я по привычке ждал, что Rails сделает так же сам.