如果我将一个after_save回调添加到ActiveRecord模型,并在该回调中使用update_attribute更改对象,则会再次调用回调,并且所以发生了“堆栈溢出”(呵呵,无法抗拒)。使用after_save回调修改同一对象而不触发回调(递归)
是否有可能避免这种行为,也许可以在执行期间禁用回调?还是有另一种方法?
谢谢!
如果我将一个after_save回调添加到ActiveRecord模型,并在该回调中使用update_attribute更改对象,则会再次调用回调,并且所以发生了“堆栈溢出”(呵呵,无法抗拒)。使用after_save回调修改同一对象而不触发回调(递归)
是否有可能避免这种行为,也许可以在执行期间禁用回调?还是有另一种方法?
谢谢!
一种解决方法是在类中设置一个变量,并在after_save中检查其值。
通过这种方式,它只会尝试保存两次。这可能会打击你的数据库两次,这可能会或可能不需要。
我有一种模糊的感觉,即有内置的东西,但这是一种相当简单的方法来防止任何应用程序中的递归特定点。 我还建议再次查看代码,因为无论你在after_save中做什么都应该在before_save中完成。有时候这不是真的,但它们相当罕见。
看看如何实现update_attribute。使用send方法代替:
send(name.to_s + '=', value)
你可以使用before_save回调吗?
如果使用before_save,则可以在保存完成之前修改任何其他参数,这意味着您不必显式调用save。
谢谢你们,问题是,我更新其他对象了(如果你愿意的兄弟姐妹)...忘了提,部分...
所以before_save是不可能的,因为如果保存失败所有修改到其他对象将不得不恢复,并可能变得凌乱:)
此代码甚至不尝试解决线程或并发问题,就像Rails正常。如果您需要该功能,请注意!
基本上,这个想法是保持计数在什么水平的递归调用“保存”你是,只有当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
这很聪明,谢谢! – Ivan 2008-10-20 22:35:02
你也可以看看插件Without_callbacks。它向AR添加了一种方法,可以让您跳过给定块的某些回调。 例如:
def your_after_save_func
YourModel.without_callbacks(:your_after_save_func) do
Your updates/changes
end
end
不知道该插件,它会派上用场,谢谢! – Ivan 2008-10-20 22:39:27
我也有这个问题。我需要保存一个取决于对象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
我没有看到这个答案,所以我想如果它有助于任何有关这个主题的搜索我会添加它。 (ScottD的without_callbacks建议很接近。)
ActiveRecord为这种情况提供了update_without_callbacks
,但它是一种私有方法。无论如何请使用send来访问它。在你保存的对象的回调中,正是使用它的原因。
而且还有另外一个SO线程这里覆盖此相当不错: How can I avoid running ActiveRecord callbacks?
有时候,这是因为在模型不指定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
键停止递归是分配从属性值和然后将该属性设置为零,以便在随后的保存中不满足:if
条件。
诀窍只是使用#update_column
:
此外,它只是简单地向数据库发出一个快速更新查询。
http://apidock.com/rails/ActiveRecord/Persistence/update_columns
可以在关联使用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
太棒了!我也在寻找内置的方法,但到目前为止似乎没有,但如果您可以设置一个特殊属性来告诉Rails暂时暂停回调,那将是非常棒的......您的方法就像这样,非常感谢! – Ivan 2008-10-19 11:37:10