使用after_save回调修改同一个对象而不再触发回调(递归)

如果我将一个after_save回调添加到ActiveRecord模型,并且在该回调上我使用update_attribute来更改对象,则再次调用回调,因此发生“堆栈溢出”(hehe,无法抗拒)。

是否可以避免这种行为,可能在执行期间禁用回调? 或者还有另一种方法吗?

谢谢!

一种解决方法是在类中设置变量,并在after_save中检查其值。

  1. 先检查一下。 (如果var)
  2. 在调用update_attribute之前将其分配给’false’值。
  3. 调用update_attribute。
  4. 将其指定为“true”值。
  5. 结束

这样,它只会尝试保存两次。 这可能会两次打到您的数据库,这可能是也可能不是。

我有一种模糊的感觉,内置了一些东西,但这是一种相当简单的方法来防止几乎任何应用程序中的特定递归点。 我还建议再次查看代码,因为很可能无论你在after_save中做什么都应该在before_save中完成。 有时候这不是真的,但它们相当罕见。

你可以使用before_save回调吗?

我没有看到这个答案,所以我想我会添加它,以防它帮助任何人搜索这个主题。 (ScottD的no_callbacks建议很接近。)

ActiveRecord为这种情况提供update_without_callbacks ,但它是一个私有方法。 无论如何,使用send来访问它。 在你正在保存的对象的回调中正是使用它的原因。

此外还有另一个SO线程可以很好地解决这个问题: 如何避免运行ActiveRecord回调?

您还可以查看插件Without_callbacks 。 它为AR添加了一种方法,允许您跳过给定块的某些回调。 例:

 def your_after_save_func YourModel.without_callbacks(:your_after_save_func) do Your updates/changes end end 

查看update_attribute的实现方式。 改为使用send方法:

 send(name.to_s + '=', value) 

如果使用before_save,则可以在保存完成之前修改任何其他参数,这意味着您不必显式调用save。

这段代码甚至没有尝试解决线程或并发问题,就像Rails本身一样。 如果您需要该function,请注意!

基本上,我们的想法是保持对“保存”的递归调用级别的计数,并且只有在退出最高级别时才允许after_save。 您还需要添加exception处理。

 def before_save @attempted_save_level ||= 0 @attempted_save_level += 1 end def after_save if (@attempted_save_level == 1) #fill in logic here save #fires before_save, incrementing save_level to 2, then after_save, which returns without taking action #fill in logic here end @attempted_save_level -= 1 # reset the "prevent infinite recursion" flag end 

谢谢大家,问题是我也更新了其他对象(兄弟姐妹,如果你愿意的话)…忘了提那个部分……

因此,before_save是不可能的,因为如果保存失败,则必须恢复对其他对象的所有修改,这可能会变得混乱:)

诀窍就是使用#update_column

  • 跳过validation。
  • 跳过回调。
  • updated_at / updated_on未更新。

此外,它只是向数据库发出一个快速更新查询。

http://apidock.com/rails/ActiveRecord/Persistence/update_columns

我也有这个问题。 我需要保存一个取决于对象id的属性。 我通过使用条件调用来解决它…

 Class Foo << ActiveRecord::Base after_save :init_bar_attr, :if => "bar_attr.nil?" # just make sure this is false after the callback runs def init_bar_attr self.bar_attr = "my id is: #{self.id}" # careful now, let's save only if we're sure the triggering condition will fail self.save if bar_attr end 

有时这是因为没有在模型中指定attr_accessible。 当update_attribute想要编辑属性时,如果发现它们不可访问并改为创建新对象。在保存新对象时,它将进入一个无休止的循环。

当记录被复制到不同的上下文时,我需要在文本块中gsub路径名:

 attr_accessor :original_public_path after_save :replace_public_path, :if => :original_public_path private def replace_public_path self.overview = overview.gsub(original_public_path, public_path) self.original_public_path = nil save end 

停止递归的关键是从属性中分配值,然后将属性设置为nil,以便在后续保存中不满足:if条件。

您可以将after_saveif关联使用,如下所示:

 after_save :after_save_callback, if: Proc.new { //your logic when to call the callback } 

要么

 after_save :after_save_callback, if: :call_if_condition def call_if_condition //condition for when to call the :after_save_callback method end 

call_if_condition是一种方法。 定义何时在该方法中调用after_save_callback的方案