【问题标题】:Determine what attributes were changed in Rails after_save callback?确定 Rails after_save 回调中更改了哪些属性?
【发布时间】: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

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

【问题讨论】:

    标签: ruby-on-rails ruby-on-rails-4 model callback observer-pattern


    【解决方案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 方法本身内添加条件。

    【讨论】:

      猜你喜欢
      • 2021-11-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-09-29
      • 1970-01-01
      相关资源
      最近更新 更多