Запустить код rails после того, как обновление базы данных заработало без after_commit
Я пытаюсь сразить некоторые расовые дела с моим менеджером фоновых задач. По сути, у меня есть объект Thing
(уже существует) и присваиваю ему некоторые свойства, а затем сохраняю его. После того, как он будет сохранен с новыми свойствами, я отправлю его в очередь в Resque, передавая идентификатор.
thing = Thing.find(1)
puts thing.foo # outputs "old value"
thing.foo = "new value"
thing.save
ThingProcessor.queue_job(thing.id)
Фоновое задание будет загружать объект из базы данных с помощью Thing.find(thing_id)
.
Проблема заключается в том, что мы обнаружили, что Resque так быстро подбирает задание и загружает объект Thing
из идентификатора, что он загружает устаревший объект. Поэтому в рамках задания вызов thing.foo
по-прежнему будет возвращать "старое значение", например, 1/100 раза (не реальные данные, но это не часто).
Мы знаем, что это раса, потому что рельсы вернутся с thing.save
, прежде чем данные действительно будут зафиксированы в базе данных (postgresql в этом случае).
Есть ли способ в Rails только выполнить код ПОСЛЕ того, как действие базы данных зафиксировано? По сути, я хочу убедиться, что к тому времени, когда Resque загрузит объект, он получит самый свежий объект. Я знаю, что это может быть достигнуто с помощью крюка after_commit
на модели Thing
, но я не хочу его там. Мне нужно, чтобы это происходило в этом конкретном контексте, а не каждый раз, когда модель фиксировала изменения в БД.
Ответы
Ответ 1
Можно также заключить транзакцию. Как и в примере ниже:
transaction do
thing = Thing.find(1)
puts thing.foo # outputs "old value"
thing.foo = "new value"
thing.save
end
ThingProcessor.queue_job(thing.id)
Обновление: есть драгоценный камень, который вызывает After Transaction, с этим вы можете решить свою проблему. Ссылка здесь:
http://xtargets.com/2012/03/08/understanding-and-solving-race-conditions-with-ruby-rails-and-background-workers/
Ответ 2
У меня была похожая проблема, когда мне нужно было убедиться, что транзакция была совершена, прежде чем выполнять серию действий. Я закончил тем, что использовал этот Драгоценный камень:
https://github.com/Envek/after_commit_everywhere
Это означало, что я мог сделать следующее:
def finalize!
Order.transaction do
payment.charge!
# ...
# Ensure that we only send out items and perform after actions when the order has defintely be completed
after_commit { OrderAfterFinalizerJob.perform_later(self) }
end
end
Ответ 3
Как обернуть try
вокруг transaction
, чтобы задание помещалось в очередь только после успеха транзакции?