确定 Rails after_save 回调中更改了哪些属性?

Posted

技术标签:

【中文标题】确定 Rails after_save 回调中更改了哪些属性?【英文标题】:Determine what attributes were changed in Rails after_save callback? 【发布时间】:2011-04-21 04:17:51 【问题描述】:

我正在我的模型观察者中设置一个 after_save 回调,以仅在模型的 published 属性从 false 更改为 true 时发送通知。由于 changed? 等方法仅在保存模型之前有用,因此我目前(但未成功)尝试这样做的方式如下:

def before_save(blog)
  @og_published = blog.published?
end

def after_save(blog)
  if @og_published == false and blog.published? == true
    Notification.send(...)
  end
end

是否有人对处理此问题的最佳方法有任何建议,最好使用模型观察者回调(以免污染我的控制器代码)?

【问题讨论】:

【参考方案1】:

您只需添加一个访问器来定义您更改的内容

class Post < AR::Base
  attr_reader :what_changed

  before_filter :what_changed?

  def what_changed?
    @what_changed = changes || []
  end

  after_filter :action_on_changes

  def action_on_changes
    @what_changed.each do |change|
      p change
    end
  end
end

【讨论】:

这是一个非常糟糕的做法,甚至不要考虑它。【参考方案2】:

Rails 5.1+

使用saved_change_to_published?:

class SomeModel < ActiveRecord::Base
  after_update :send_notification_after_change

  def send_notification_after_change
    Notification.send(…) if (saved_change_to_published? && self.published == true)
  end

end

或者,如果您愿意,saved_change_to_attribute?(:published)

导轨 3–5.1

警告

这种方法适用于 Rails 5.1(但在 5.1 中已弃用,在 5.2 中有重大更改)。您可以在 pull request 中阅读有关更改的信息。

在模型上的after_update 过滤器中,您可以使用_changed? 访问器。比如:

class SomeModel < ActiveRecord::Base
  after_update :send_notification_after_change

  def send_notification_after_change
    Notification.send(...) if (self.published_changed? && self.published == true)
  end

end

它只是工作。

【讨论】:

忘记我上面所说的——它在 Rails 2.0.5 中不起作用。这是 Rails 3 的一个有用的补充。 我认为 after_update 现在已被弃用?无论如何,我在 after_save 钩子中尝试了这个,它似乎工作正常。 (显然,changes() 哈希在 after_save 中仍未被重置。) 我必须在 model.rb 文件中包含这一行。 包括 ActiveModel::Dirty 在更高版本的 Rails 中,您可以将条件添加到 after_update 调用:after_update :send_notification_after_change, if: -&gt; published_changed? Rails 5.2 中会有一些 API 变化。您必须在回调期间执行 saved_change_to_published?saved_change_to_published 来获取更改【参考方案3】:

“选定”的答案对我不起作用。我正在使用带有 CouchRest::Model 的 rails 3.1(基于 Active Model)。 _changed? 方法不会为 after_update 钩子中的更改属性返回 true,仅在 before_update 钩子中。我能够使用(新的?)around_update 钩子让它工作:

class SomeModel < ActiveRecord::Base
  around_update :send_notification_after_change

  def send_notification_after_change
    should_send_it = self.published_changed? && self.published == true

    yield

    Notification.send(...) if should_send_it
  end

end

【讨论】:

选择的答案对我也不起作用,但确实如此。谢谢!我正在使用 ActiveRecord 3.2.16。【参考方案4】:

如果您可以在 before_save 而不是 after_save 上执行此操作,您将可以使用它:

self.changed

它返回此记录中所有已更改列的数组。

你也可以使用:

self.changes

它以数组形式返回发生变化的列的哈希值以及结果之前和之后的值

【讨论】:

除了这些在after_ 回调中不起作用,这正是问题所在。 @jacek-głodek 下面的答案是正确的。 更新了答案以明确这仅适用于before_save 这是哪个 Rails 版本?在 Rails 4 中,self.changed 可以在 after_save 回调中使用。 效果很好!还要注意的是,self.changed 的结果是一个字符串数组! (不是符号!)["attr_name", "other_attr_name"]【参考方案5】:

对于那些想知道刚刚在 after_save 回调中所做的更改的人:

Rails 5.1 及更高版本

model.saved_changes

导轨
model.previous_changes

另见:http://api.rubyonrails.org/classes/ActiveModel/Dirty.html#method-i-previous_changes

【讨论】:

当您不想使用模型回调并且需要在执行进一步功能之前进行有效保存时,这非常有效。 这太完美了!感谢您发布此内容。 太棒了!谢谢你。 要明确一点:在我的测试(Rails 4)中,如果您使用after_save 回调,self.changed?trueself.attribute_name_changed? 也是true,但@ 987654330@ 返回一个空的哈希值。 这在 Rails 5.1 + 中已被弃用。在 after_save 回调中使用 saved_changes 代替【参考方案6】:

对于以后看到这一点的任何人,因为它目前(2017 年 8 月)在谷歌上名列前茅:值得一提的是,这种行为将在 Rails 5.2 中进行更改,并且从 Rails 开始有弃用警告5.1,ActiveModel::Dirty 发生了一些变化。

我要改变什么?

如果您在after_*-callbacks 中使用attribute_changed? 方法,您会看到如下警告:

弃用警告:attribute_changed? 在 after 回调中的行为将在下一版本的 Rails 中发生变化。新的返回值将反映在 save 返回后调用该方法的行为(例如,与现在返回的相反)。要保持当前行为,请改用saved_change_to_attribute?。 (从 /PATH_TO/app/models/user.rb:15 的 some_callback 调用)

正如它所提到的,您可以通过将函数替换为 saved_change_to_attribute? 来轻松解决此问题。例如,name_changed? 变为 saved_change_to_name?

同样,如果您使用 attribute_change 获取前后值,这也会发生变化并抛出以下内容:

弃用警告:attribute_change 在 after 回调中的行为将在下一版本的 Rails 中发生变化。新的返回值将反映在 save 返回后调用方法的行为(例如,与现在返回的相反)。要保持当前行为,请改用saved_change_to_attribute。 (从 /PATH_TO/app/models/user.rb:20 的 some_callback 调用)

同样,正如它所提到的,该方法将名称更改为saved_change_to_attribute,它返回["old", "new"]。 或者使用saved_changes,它返回所有的变化,这些可以通过saved_changes['attribute']访问。

【讨论】:

请注意,这个有用的回复还包括弃用 attribute_was 方法的解决方法:改用 saved_change_to_attribute【参考方案7】:

您可以像这样向after_update 添加条件:

class SomeModel < ActiveRecord::Base
  after_update :send_notification, if: :published_changed?

  ...
end

无需在send_notification 方法本身内添加条件。

【讨论】:

以上是关于确定 Rails after_save 回调中更改了哪些属性?的主要内容,如果未能解决你的问题,请参考以下文章

Rails ActiveRecord 在 after_save 回调中使用最近保存的记录 ID [关闭]

具有虚拟属性的 after_save 回调 Rails 3

Rails - 如何从after_save回调中查看旧模型值和新模型值?

Rails after_save 错误

Rails 3:取消在 after_save 上的插入

插入后的 Rails 回调