【发布时间】:2008-10-19 02:06:52
【问题描述】:
如果我向 ActiveRecord 模型添加 after_save 回调,并在该回调中使用 update_attribute 更改对象,则再次调用回调,因此会发生“堆栈溢出”(呵呵,无法抗拒)。
是否有可能避免这种行为,也许在执行期间禁用回调?还是有其他方法?
谢谢!
【问题讨论】:
标签: ruby-on-rails
如果我向 ActiveRecord 模型添加 after_save 回调,并在该回调中使用 update_attribute 更改对象,则再次调用回调,因此会发生“堆栈溢出”(呵呵,无法抗拒)。
是否有可能避免这种行为,也许在执行期间禁用回调?还是有其他方法?
谢谢!
【问题讨论】:
标签: ruby-on-rails
一种解决方法是在类中设置一个变量,并在 after_save 中检查它的值。
这样,它只会尝试保存两次。这可能会两次访问您的数据库,这可能是可取的,也可能是不可取的。
我有一种模糊的感觉,就是内置了一些东西,但这是一种相当简单的方法,可以防止几乎任何应用程序中的特定递归点。 我还建议再次查看代码,因为您在 after_save 中所做的任何事情都应该在 before_save 中完成。有时这不是真的,但它们相当罕见。
【讨论】:
您能改用 before_save 回调吗?
【讨论】:
我没有看到这个答案,所以我想我会添加它,以防它帮助任何人搜索这个主题。 (ScottD 的 without_callbacks 建议很接近。)
ActiveRecord 针对这种情况提供了update_without_callbacks,但它是一个私有方法。无论如何,请使用 send 访问它。正在保存的对象的回调中正是使用它的原因。
这里还有另一个 SO 线程很好地涵盖了这一点: How can I avoid running ActiveRecord callbacks?
【讨论】:
您也可以查看插件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 本身一样。如果您需要该功能,请注意!
基本上,我们的想法是记录您的“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
【讨论】:
谢谢大家,问题是我也更新了 other 对象(如果你愿意的话,兄弟姐妹)...忘记提及那部分...
所以 before_save 是不可能的,因为如果保存失败,则必须恢复对其他对象的所有修改,这可能会变得混乱 :)
【讨论】:
诀窍就是使用#update_column:
此外,它只是向数据库发出一个快速更新查询。
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_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 的场景
【讨论】: