I have problem with storing data into Postgres using update_all.
To explain the problem, we have 2 classes, Meter and Readings. Every meter has many readings. Meter has attributes unit, like energy unit kWh, MWh, ..., and multiplier, number that multiplies readings state to get final value.
When user wants to update Meter params (unit, multiplier), we use Interactors to firstly update Readings states and then save Meter itself.
All these operations happen in single transaction, so if one fails, then all fail.
But we came in situation, when meter is saved and readings are not updated or vice versa.
I checked, that when meter does not save correctly, it causes context.fail!. Readings uses update_all without any check of success, but I read, that update_all goes directly to the DB and when it fails on constraints, it fails with an exception.
I did not find any way, how to replicate it.
// 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
My idea is to use something like this into 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
// ...
but I have no idea, how to prove it.
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
(comment can't format, so here it is in an answer)
I don't see a transaction. Without unwinding your code, I don't understand why this isn't just