如何跳过ActiveRecord回调?

85

可能是重复问题:
如何避免运行ActiveRecord回调函数?

我有这样的模型:

class Vote < ActiveRecord::Base  
    after_save :add_points_to_user

    .....
end

是否有办法强制模型在保存时跳过调用add_points_to_user方法?可能类似于ActiveRecord#deleteActiveRecord#destroy的区别吗?


1
依我之见,这个被认为已经有答案的问题,其实只有错误的答案。因此,我认为这并不算是重复问题。我认为下面提供的答案是官方认可的回调避免方法。 - sheldonh
4个回答

174

对于Rails 3,ActiveSupport::Callbacks 可以提供所需的控制。我刚刚在一个数据集成场景中面临着同样的挑战,需要跳过本来很有用的回调函数。你可以批量重置回调函数或使用 skip_callback 来进行有选择性地禁用,像这样:

Vote.skip_callback(:save, :after, :add_points_to_user)

之后您可以对投票实例进行操作,但会禁止: 向用户添加积分


49
如果您只是想暂时跳过一个 create/save 的回调函数,而不是永久性地将其删除,则需要在跳过回调函数后重新启用它: User.set_callback :save, :after, :add_points_to_user 请注意,如果在这两个调用之间执行的任何操作可能引发异常,您应该在其周围加上 begin(-rescue)-ensure 块。 - Timo
9
有没有针对特定实例的类似功能? - EyalB
7
我建议的是,在类上使用 skip_callback 声明性地设置一个 :if 选项,如 if: lambda { @skip_callbacks == true },并编写包装需要执行此操作的方法:def some_special_operation!; @skip_callbacks = true; do_something; save!; @skip_callbacks = false; end。可以使用上下文管理器方法来设置/取消实例变量并生成块,但如果您经常需要这样做,则可能存在某些问题;-) 编辑 哎呀,我讨厌SO评论换行限制。 - ches
5
注意skip_callbackset_callback如何影响回调序列。如果我正确地阅读了源代码,它们实际上会删除并重新添加回调。默认情况下,回调将被添加到链的末尾。在设置时,您可以使用:prepend => :true将其强制添加到链的开头。如果skip/set实际上进行了停用/重新启用而不影响顺序,那就太好了。我的示例在这个答案中。 - Mark Berry
5
这不是线程安全的,所以如果你在多线程服务器上运行此代码,请小心。 - Arjan
1
对于特定的实例,我使用了一个瞬态属性。我在模型中添加了以下这些行: attr_accessor :skip_my_callbackafter_save :my_callback, unless: :skip_my_callback然后在需要跳过它时,在保存之前设置 my_instance.skip_my_callback = true - Rachel Hogue

47

以下适用于Rails 2、Rails 3和Rails 4:

http://guides.rubyonrails.org/v3.2.13/active_record_validations_callbacks.html#skipping-callbacks

该网页提供了一个跳过回调的方法列表,并解释了不加谨慎使用这些方法会带来危险。根据知识共享署名-相同方式共享3.0许可条款,在此处重印。

12 跳过回调

与验证类似,也可以跳过回调。但是,应该谨慎使用这些方法,因为重要的业务规则和应用程序逻辑可能保存在回调中。不了解潜在影响地绕过它们可能会导致数据无效。

  • decrement
  • decrement_counter
  • delete
  • delete_all
  • find_by_sql
  • increment
  • increment_counter
  • toggle
  • touch
  • update_column
  • update_all
  • update_counters

据我所知,在Rails 4.2中,toggle只会更改对象,但不保存到数据库中,您需要在toggle后调用save方法。如果使用toggle!,它将立即保存,但会触发回调,因此应将其从列表中删除。 - zw963
@zw963 但“toggle!”不在列表中,因此无需删除。您是说非bang-toggle也会触发回调,因此需要删除吗?Rails关于ActiveRecord回调的指南在4.2.1中仍将toggle列为跳过回调的方法之一。 - sheldonh
我的理解是,某些方法可以将数据保存到数据库中,而不触发回调函数,它应该在列表中。但是,切换操作并不保存数据。 - zw963
啊,现在我明白了。不,这不是一个修改数据库的方法列表。它是一个可能修改模型状态(已持久化或未持久化)但不触发与修改属性相关联的回调函数的方法列表。我感谢原始问题特别问到如何跳过保存时的验证,但我更愿意不编辑引用的文本。 - sheldonh

28
这将跳过您的验证:
vote.save(:validate => false)

更多信息请点击这里

如果要跳过回调和验证,可以使用 update_column v(3.1) 或 update_all。

vote = Vote.first
vote.update_column(:subject, 'CallBacks')

显然,这只适用于ActiveRecord 3.1。

或者:

Vote.where('id = ?', YourID).update_all(:subject => 'CallBacks')

最后还有一个finally选项,这会跳过所有步骤:

execute "UPDATE votes SET subject = 'CallBacks' WHERE id = YourID"

好的,最后一个不太美观。


10
这不会跳过回调函数,仅仅是跳过了验证。 - pdu
1
update_all会跳过回调,详情请见http://apidock.com/rails/ActiveRecord/Relation/update_all:“它不会实例化涉及的模型,也不会触发Active Record回调或验证。” - Kasper Grubbe
4
update_column 也可以使用,只是上面的语法是不正确的。它需要两个参数:vote.update_column(:subject, 'CallBacks')。 - yuяi

28

对于 Rails 2,但不适用于 Rails 3,您可以使用以下代码:

object.send(:create_without_callbacks)
object.send(:update_without_callbacks)

7
很遗憾,在Rails 3中,这不再起作用了 :( - Spike Gronim
2
请查看我的答案,其中包含适用于Rails 2和Rails 3的建议。 - sheldonh
3
“Model”这个词很误导人,那些方法是针对 AR 实例的,而不是类方法,所以应该使用“record.send(:create_without_callbacks)”来调用。 - tokland
@tokland,刚看到这个答案,受益于你的评论,已经更新了答案。 - Matt

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