28

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

是否可以避免这种行为,也许在执行期间禁用回调?还是有其他方法?

谢谢!

4

13 回答 13

13

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

  1. 先检查一下。(如果是 var)
  2. 在调用 update_attribute 之前将其分配给“假”值。
  3. 调用 update_attribute。
  4. 将其分配给“真实”值。
  5. 结尾

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

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

于 2008-10-19T05:25:32.593 回答
10

您可以改用 before_save 回调吗?

于 2008-10-19T08:09:21.967 回答
10

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

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

这里还有另一个 SO 线程很好地涵盖了这一点: 如何避免运行 ActiveRecord 回调?

于 2009-10-06T23:10:17.397 回答
7

您也可以查看Without_callbacks插件。它向 AR 添加了一种方法,可让您跳过给定块的某些回调。例子:

def your_after_save_func
  YourModel.without_callbacks(:your_after_save_func) do
    Your updates/changes
  end
end
于 2008-10-20T19:27:14.347 回答
6

查看update_attribute是如何实现的。请改用发送方法:

send(name.to_s + '=', value)
于 2008-10-19T02:10:34.127 回答
3

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

于 2008-10-19T11:18:11.273 回答
3

这段代码甚至没有尝试解决线程或并发问题,就像 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
于 2008-10-19T18:16:16.643 回答
2

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

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

于 2008-10-19T11:34:36.780 回答
2

诀窍就是使用#update_column

  • 验证被跳过。
  • 回调被跳过。
  • updated_at/updated_on 未更新。

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

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

于 2015-04-15T18:02:59.493 回答
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
于 2009-08-11T21:14:20.547 回答
1

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

于 2011-12-19T17:15:40.077 回答
0

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在后续保存时不满足条件。

于 2012-12-10T03:08:40.047 回答
0

您可以使用以下after_save关联if

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方法的场景

于 2016-10-03T06:50:03.867 回答