Запустить код 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, чтобы задание помещалось в очередь только после успеха транзакции?