update_all如何在不引发异常的情况下失败?

时间:2019-01-24 21:45:54

标签: ruby-on-rails ruby database update-all

我无法使用update_all将数据存储到Postgres中。 为了解释该问题,我们有2个类,MeterReadings。每个仪表都有许多读数。仪表具有属性unit,例如能量单位kWh,MWh,...和multiplier,该数字乘以读数状态以获得最终值。 当用户想要更新Meter参数(unitmultiplier)时,我们使用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 :(得分:1)

(评论无法格式化,因此在答案中)

我没有交易。没有展开您的代码,我不明白为什么这不只是

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

答案 1 :(得分:0)

问题全与线程有关。我们发现,该问题在过去几年中仅发生过几次。所以这确实是一个极端的情况。

有两个操作。提到的一种是用户更新仪表参数,另一种是从实际设备(物理仪表)自动导入读数。 导入开始时,它将选择单位和乘数(动作1),修改读数并将其保存到DB中(动作2)。但是这两个动作不在同一事务中。因此,如果用户在这两个动作之间保存了仪表,我们将保存错误的数据,即更新单位或乘数的系数。

我们解决了在与meter.reload的交易中使用readings.save的问题。我们将重新加载之前和之后的仪表进行比较,如果更改了,我们必须重新计算读数。