如何确定记录在 after_save 中是新创建的还是更新的?

120

#new_record?函数用于判断记录是否已保存,但在after_save钩子中,它总是返回false。有没有办法确定记录是新创建的还是从更新修改的旧记录?

我希望不使用另一个回调函数,如before_create在模型中设置标志或要求对数据库进行另一个查询。

任何建议都将不胜感激。

编辑:需要在after_save钩子中确定,并且对于我的特定用例,没有updated_atupdated_on时间戳。


1
也许可以在 before_save 中传递一个参数?只是随口一想。 - Trip
persisted? - aerijman
8个回答

197

我想在after_save回调中使用这个。

一个更简单的解决方案是使用id_changed?(因为它不会在update上改变),甚至可以使用created_at_changed? 如果存在时间戳列。

更新:正如@mitsy指出的那样,如果需要在回调之外进行此检查,则使用id_previously_changed?。请参见文档


3
通过 ActiveModel::Dirty 记录模型对象属性的变化情况。 - chaserx
9
最好用after_update和after_create进行区分。回调可以共享一个通用方法,该方法需要一个参数来指示是创建还是更新。 - matthuhiggins
3
after_save 之外,这两个领域都无法正常工作。 - fatuhoku
4
id_changed? will be false after the record is saved (outside of the hooks at least). In that case, you can use id_previously_changed? - mltsy
2
看起来 id_changed? 被弃用了,推荐使用 saved_change_to_id?。在使用 id_changed? 后,我在控制台上看到了以下消息:“DEPRECATION WARNING: The behavior of attribute_changed? inside of after callbacks will be changing in the next version of Rails. The new return value will reflect the behavior of calling the method after save returned (e.g. the opposite of what it returns now). To maintain the current behavior, use saved_change_to_attribute? instead.” - theblang
显示剩余6条评论

33

在我所知道的情况下,这里没有Rails的魔法,您需要自己完成。您可以使用虚拟属性来清理代码...

在您的模型类中:

def before_save
  @was_a_new_record = new_record?
  return true
end

def after_save
  if @was_a_new_record
    ...
  end
end

30

对于那些拥有updated_at时间戳的人,还有另一种选择:

if created_at == updated_at
  # it's a newly created record
end

不是一个坏主意,但在某些情况下可能会适得其反(并非百分之百可靠)。 - Ash Blue
根据迁移运行的时间,created_at和updated_at记录可能会出现偏差。此外,您始终有机会在最初保存记录后立即更新记录,这可能会使时间不同步。这并不是一个坏主意,只是感觉可以添加更加牢固的实现。 - Ash Blue
它们可能在记录最初创建后相等。如果一条记录几个月没有更新,它看起来就像是“刚刚创建”。 - bschaeffer
@bschaeffer 很好的想法。不过在问题的范围内(一个“after_save”回调函数),这是可能的吗? - colllin
1
@bschaeffer 抱歉,我的问题是“在after_save回调中,除了第一次创建时,是否可能在任何其他时间使created_at等于updated_at?” - colllin
1
@colllin:创建记录时,在after_save回调中,created_atupdated_at将相等。在其他所有情况下,在after_save回调中它们不会相等。 - bschaeffer

23

有一个after_create回调函数,只在记录是新记录时,在记录保存后才会被调用。如果这是一个已经存在并发生更改和保存的记录,则可以使用after_update回调函数。无论是after_create还是after_update被调用后,都会调用after_save回调函数。

如果需要在新记录保存后执行一次操作,请使用after_create

更多信息请参见:http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html


4
实际上可能并非如此。如果您在创建中有关联,那么after_create会在关联被创建之前被调用,因此,如果您需要确保所有内容都已创建,则需要使用after_save - Niels Kristian
对这个答案表示不满,因为它完全是错误的。在大多数情况下,当调用after_create时,记录还没有被存储到数据库中。 - svelandiag

19

由于对象已经保存,因此您需要查看之前的更改。ID仅在创建后更改。

# true if this is a new record
@object.previous_changes[:id].any?

还有一个实例变量@new_record_before_save。您可以通过以下方式访问它:

# true if this is a new record
@object.instance_variable_get(:@new_record_before_save)

这两种方式都不太好看,但它们可以让你知道对象是否是新创建的。希望有所帮助!


我目前正在使用Rails 4中的byebug,在一个after_save回调中,但是这两个都无法识别这条新记录。 - MCB
我认为@object.previous_changes[:id].any?非常简单而优雅。在记录更新后它对我很有效(我没有从after_save中调用它)。 - thekingoftruth
1
@object.id_previously_changed? 稍微好看了一点。 - aNoble
在 Rails 4 中的 after_save 中,您需要查看 changes[:id] 而不是 previous_changes[:id]。然而,在 Rails 5.1 中将发生更改(请参见 https://github.com/rails/rails/pull/25337 中的讨论)。 - gmcnaughton
1
为了更好地理解,previous_changes.key?(:id) - Sebastián Palma

14

Rails 5.1+ 的方式:

user = User.new
user.save!
user.saved_change_to_attribute?(:id) # => true

1
这正是我正在寻找的。谢谢! - Felipe Zavan

14

1
对于Rails 4(在4.2.11.1上进行了检查),在after_save内创建对象时,changesprevious_changes方法的结果是空哈希{}。因此,像id_changed?这样的attribute_changed?方法将无法按预期工作。
但是,您可以利用这个知识 - 知道至少有1个属性必须在更新中出现在changes中 - 检查changes是否为空。一旦确认为空,您必须正在创建对象:
after_save do
  if changes.empty?
    # code appropriate for object creation goes here ...
  end
end

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