Как может update_all завершиться ошибкой, не вызывая исключение? - PullRequest
0 голосов
/ 25 января 2019

У меня проблема с сохранением данных в Postgres с использованием update_all. Чтобы объяснить проблему, у нас есть 2 класса, Meter и Readings. Каждый метр имеет много показаний. Измеритель имеет атрибуты unit, такие как единица энергии кВтч, МВтч, ... и multiplier, число, которое умножает состояние показаний, чтобы получить окончательное значение. Когда пользователь хочет обновить Meter params (unit, multiplier), мы используем Interactors, чтобы сначала обновить Readings состояния, а затем сохранить Meter. Все эти операции выполняются в одной транзакции, поэтому, если одна из них завершается неудачей, то все неудачны Но мы попали в ситуацию, когда счетчик сохраняется, а показания не обновляются или наоборот. Я проверил, что, когда счетчик не сохраняет правильно, это вызывает context.fail!. Readings использует update_all без какой-либо проверки на успех, но я прочитал, что update_all отправляется непосредственно в БД, а когда он не работает с ограничениями, происходит сбой с исключением.

Я не нашел способа, как его воспроизвести.

// update readings
class Meters::ChangeUnit
   // includes

   def call
      coefficient = 1.0
      coefficient *= unit_change if context.meter.energy_unit_changed?
      coefficient *= multiplier_change if context.meter.multiplier_changed?

      return if coefficient == 1.0

      // this probably fails:
      context.meter.readings.update_all "state = state * #{coefficient}"
   end

   // ...

end

// save meter
class Meters::Save
    include Meters::BaseInteractor

    def call
        context.fail! meter_errors: context.meter.errors unless context.meter.save
    end
end

Моя идея состоит в том, чтобы использовать что-то подобное в Meters::ChangeUnit call:

 // ...
 cnt = context.meter.readings.count
 updated = context.meter.readings.update_all "state = state * #{coefficient}"
 unless cnt == updated
   context.fail! updated_meter_readings: "#{updated}/#{cnt}"
 end
 // ...

но я понятия не имею, как это доказать.

EDIT1:

 // usage in cotroller
 context = UpdateMeter.call(meter: @meter, bonds_definition: params[:meters_ids])


 // UpdateMeter
 class UpdateMeter
    include Interactor::Organizer

    organize Meters::Update, ProcessAfterCommitQueue
 end




// Meters::Update
class Meters::Update
  include Interactor::Organizer
  include Interactor::InTransaction

  organize Meters::ValidateActive,
           // ...
           Meters::ChangeUnit,
           // ...
           Meters::Save,
           // ...
end



// Interactor::InTransaction
module Interactor::InTransaction
   extend ActiveSupport::Concern

   included do
      around do |interactor|
         ActiveRecord::Base.transaction { interactor.call }
      end
   end
end

Ответы [ 2 ]

0 голосов
/ 27 января 2019

Проблема была в нитях.Мы выяснили, что эта проблема возникала всего несколько раз за последние несколько лет.Так что это был действительно крайний случай.

Есть две операции.Один из них упомянут обновлением параметров счетчика пользователем, а другой - автоматический импорт показаний с реальных устройств (физических счетчиков).Когда начинается импорт, он выбирает единицу измерения и множитель (действие 1), изменяет показания и сохраняет их в БД (действие 2).Но эти 2 действия не в одной транзакции.Таким образом, если бы пользователь сохранил счетчик между этими двумя действиями, мы сохранили бы неправильные данные, потому что обновленная единица измерения или множитель.

Мы решили, что с помощью meter.reload в транзакции с readings.save.Мы сравниваем счетчик до перезагрузки и после перезагрузки, и если он изменился, мы должны пересчитать показания.

0 голосов
/ 25 января 2019

(комментарий не может быть отформатирован, поэтому он есть в ответе)

Я не вижу транзакции. Не раскручивая ваш код, я не понимаю, почему это не просто

Meter.transaction do
  context.meter.save
  interactor.call
end
...