У меня проблема с сохранением данных в 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