使用after_save回调函数修改同一对象,而不会触发回调函数(递归)

29
如果我在ActiveRecord模型中添加一个after_save回调函数,并且在该回调函数中使用update_attribute来更改对象,那么回调函数会再次被调用,从而导致“堆栈溢出”错误(呵呵,忍不住要说一下)。
有没有可能避免这种行为,比如在执行期间禁用回调函数?或者还有其他方法吗?
谢谢!
13个回答

13

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

  1. 先检查它(如果 var)。
  2. 在调用 update_attribute 之前将其赋值为“false”。
  3. 调用 update_attribute。
  4. 将其赋值为“true”。
  5. 结束。

这样,它只会尝试保存两次。这很可能会对您的数据库进行两次访问,这可能是需要的,也可能不需要。

我模糊地感觉到有内置的解决方案,但这是一种相当可靠的方式,可以防止几乎任何应用程序中特定递归点的递归。我还建议再次查看代码,因为很可能您在 after_save 中执行的任何操作都应该在 before_save 中完成。虽然这样做不是总是正确的,但情况比较少见。


太棒了!我也搜索了内置方法,但目前似乎没有,但如果您可以设置一个特殊属性来告诉Rails暂时挂起该回调,那将是很好的...您的方法有点像这样,非常感谢! - Ivan

11

我没有看到这个回答,所以我想添加一下,以防有人在搜索此主题时需要帮助。(ScottD的without_callbacks建议接近解决方法。)

ActiveRecord为此情况提供了update_without_callbacks,但它是一个私有方法。使用send方法获得访问权限。正是因为在保存对象的回调内部,所以才要使用它。

另外,在这里还有一个很好地涵盖了此问题的SO线程: 如何避免运行ActiveRecord回调?


11

你可以使用before_save回调函数吗?


7

另外,您还可以查看插件Without_callbacks。它为AR添加了一个方法,让您可以跳过给定块的某些回调。

例如:

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

6

查看update_attribute的实现方式。使用send方法代替:

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

4
这段代码甚至没有尝试解决线程或并发问题,就像Rails本身一样。如果您需要这个功能,请注意!基本上,想法是保持"save"递归调用的计数,只有在退出最顶层时才允许after_save。您还需要添加异常处理。
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

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

2

2

谢谢大家,问题在于我也更新了其他对象(如果你愿意称其为兄弟对象的话)…忘了提到这一点…

所以在保存之前进行操作已经不可行了,因为如果保存失败,所有对其他对象的修改都必须被撤回,这可能会变得混乱 :)


1

我也遇到了这个问题。我需要保存一个依赖于对象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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接