【问题标题】:How to save a model without running callbacks in Rails如何保存模型而不在 Rails 中运行回调
【发布时间】:2014-01-27 01:44:24
【问题描述】:

在 Rails 中保存模型时,我需要计算值。所以我调用calculate_averages 作为Survey 类的回调:

before_save :calculate_averages

但是,有时(最初我有 10k 条记录需要此操作)我需要手动更新每条记录的所有平均值。没问题,我有如下代码:

Survey.all.each do |survey|
  survey.some_average = (survey.some_value + survey.some_other_value) / 2.to_f
  #and some more averages...
  survey.save!
end

在运行这段代码之前,我担心calculate_averages 会被调用并复制它,甚至可能会导致我做事的方式出现一些问题。好的,所以我想,好吧,我什么也不做,让calculate_averages 被调用并做它的事情。问题是,首先,即使您没有对记录进行任何更改,是否有办法强制调用回调?

其次,计算平均值的方式更有效,根本不让回调被调用并一次性计算所有内容的平均值。这可以不让回调被调用吗?

【问题讨论】:

  • 如果您有 10k 条记录要更新,那么为什么不完全绕过 AR,只做一个简单的 SQL UPDATE 呢?特别是如果你想跳过回调。
  • @muistooshort - 我简化了我需要做的计算,但它们很复杂并且也依赖于其他记录。我需要 Ruby。

标签: ruby-on-rails ruby-on-rails-3 activerecord ruby-on-rails-3.2 rails-activerecord


【解决方案1】:

我相信您的要求可以通过ActiveSupport::Callbacks 实现。看看set_callbackskip_callback

为了“即使您没有更改记录也强制调用回调”,您需要将回调注册到某些事件,例如save, validate etc.

set_callback :save, :before, :my_before_save_callback

要跳过before_save 回调,您可以:

Survey.skip_callback(:save, :before, :calculate_average). 

请在set_callbackskip_callback 引用其他支持的选项(例如条件和块)链接的ActiveSupport::Callbacks

【讨论】:

  • 为什么注册回调会强制回调被调用,即使您没有对其进行任何更改?
  • 好吧,变化并不是唯一可以触发回调的事件。因为为回调注册了一个事件,所以回调应该会被调用,不是吗?
  • 还有什么其他变化可以触发before_save回调?
  • 触发回调的不是变化,而是事件。本例中的事件是save 事件。所以无论何时保存或保存!被调用,*_save 回调被触发。
【解决方案2】:

要禁用集体回调,请使用...

Survey.skip_callback(:save, :before, :calculate_averages)

然后启用它们...

Survey.set_callback(:save, :before, :calculate_average)

这会跳过/设置所有实例。

【讨论】:

  • 似乎即使我希望它也不会调用回调(如果在重新计算平均值的过程中创建了新记录)?
  • 您可以在迭代集合的操作周围使用这两个调用。暂时禁用特定的回调以提高性能。
  • 我理解这个过程,但是当我需要在那个时间窗口内调用回调时,这不会让我不调用回调吗?
  • 这仅限于运行计算的 Rails 实例。其他任何地方发生的任何事情都不会受到影响。
  • 这不是skip_callback 命令的有效语法。第一个参数不是您定义的方法的名称,它是回调阶段之一,例如。 savevalidatecommit 等。正确的语法是 Survey.skip_callback(:save, :before, :calculate_averages) 请参阅:api.rubyonrails.org/classes/ActiveSupport/Callbacks/…
【解决方案3】:

update_column 是一个 ActiveRecord 函数,它不运行任何回调,也不运行验证。

【讨论】:

  • 看起来 update_all 很相似,允许我一次更新多个列。
  • 是的,烦人的是我必须为每条记录进行 15 次 sql update 调用。
  • update_column 只是调用 update_columns(复数)。如果您想跳过 all 回调和验证,这是无需验证或回调即可保存的正确方法。这对于像翻转布尔值这样的事情非常有用。还可以查看 touch 方法,它只是更新 updated_at 列。
【解决方案4】:

不适用于 Rails 5

Survey.skip_callback(:save, :before, :calculate_average) 

适用于 Rails 5

Survey.skip_callback(:save, :before, :calculate_average, raise: false)

https://github.com/thoughtbot/factory_bot/issues/931

【讨论】:

    【解决方案5】:

    如果您想在检查每个调查后有条件地跳过回调,您可以编写自定义方法。

    例如。

    修改后的回调

    before_save :calculate_averages, if: Proc.new{ |survey| !survey.skip_callback }
    

    新的实例方法

    def skip_callback(value = false)
      @skip_callback = @skip_callback ? @skip_callback : value
    end
    

    更新调查的脚本

    Survey.all.each do |survey|
      survey.some_average = (survey.some_value + survey.some_other_value) / 2.to_f
      #and some more averages...
      survey.skip_callback(true)
      survey.save!
    end
    

    它有点 hack,但希望对你有用。

    【讨论】:

      【解决方案6】:

      Rails 5.2.3 需要一个派对脚本来不触发模型事件,update_column(column_name, value) 成功了:

      task.update_column(task_status, ReferenceDatum::KEY_COMPLETED)
      

      https://apidock.com/rails/ActiveRecord/Persistence/update_column

      【讨论】:

        【解决方案7】:

        希望这就是你要找的。​​p>

        https://stackoverflow.com/a/6587546/2238259

        对于您的第二个问题,我怀疑最好检查何时需要进行此计算,最好可以在网络流量处于低谷的指定时间批量处理。

        编辑:糟糕。我实际上找到了 2 个链接,但显然丢失了第一个链接。希望你把它修好了。

        【讨论】:

        • 第一个链接指向完全不相关的东西。第二个链接虽然信息丰富,但看起来我最好的选择是使用survey.update_all(field: value) 机制,因为它不会在更新后调用回调。
        【解决方案8】:

        For Rails 3 ActiveSupport::Callbacks 为您提供必要的控制。您可以reset_callbacks 集体使用,或使用skip_callback 像这样明智地禁用:

        Vote.skip_callback(:save, :after, :add_points_to_user)
        

        ...之后,您可以在 :add_points_to_user 被禁止的情况下对投票实例进行操作

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-01-28
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-05-19
          • 1970-01-01
          相关资源
          最近更新 更多