Rails after_saveコールバックで変更された属性を特定しますか?


153

モデルの公開済み属性がfalseからtrueに変更された場合にのみ通知を送信するように、モデルオブザーバーでafter_saveコールバックを設定しています。などの方法が変わったのでモデルが保存される前にのみ有用ですが、私が現在(そして失敗して)それを試みている方法は次のとおりです:

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

これを処理するための最良の方法については、誰かが提案します(できれば、モデルオブザーバーのコールバックを使用して(コントローラーコードを汚染しないようにするため))。

回答:


182

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)

Rails 3–5.1

警告

このアプローチはRails 5.1で機能します(ただし、5.1では非推奨であり、5.2では重大な変更が加えられています)。このプルリクエストの変更について読むことができます。

あなたには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への便利な追加
stephenr

4
after_updateは現在廃止されていると思いますか?とにかく、私はafter_saveフックでこれを試しましたが、それはうまくいくように見えました。(changes()ハッシュは、どうやらafter_saveでまだリセットされていません。)
Tyler Rick

3
この行をmodel.rbファイルに含める必要がありました。ActiveModel :: Dirtyを含む
coderVishal

13
Railsのそれ以降のバージョンでは、に条件を追加することができますafter_update:コールafter_update :send_notification_after_change, if: -> { published_changed? }
公園。

11
Rails 5.2ではAPIが変更される予定です。変更を行うsaved_change_to_published?saved_change_to_published、コールバック中に変更をフェッチする必要があります
alopez02

183

after_saveコールバックで行われた変更を知りたい場合:

Rails 5.1以降

model.saved_changes

Rails <5.1

model.previous_changes

次も参照してください:http : //api.rubyonrails.org/classes/ActiveModel/Dirty.html#method-i-previous_changes


2
これは、モデルコールバックを使用したくない場合に完全に機能し、さらに機能を実行する前に有効な保存を行う必要があります。
Dave Robertson、

これは完璧です!これを投稿していただきありがとうございます。
jrhicks

驚くばかり!これをありがとう。
Daniel Logan

4
ただ、明確にする:あなたが使用している場合は、私のテスト(Railsの4)で、after_saveコールバックを、self.changed?であるtrueself.attribute_name_changed?もあるtrueが、self.previous_changes空のハッシュを返します。
sandre89 2016年

9
これはRails 5.1以降では非推奨です。使用saved_changesafter_saveの代わりにコールバック
rico_mac

71

現在(2017年8月)のように、これを後で見ている人にとってはgoogle が上位です:ActiveModel :: Dirtyが少し変更されたため、この動作はRails 5.2で変更され、Rails 5.1の時点で非推奨の警告があることに言及する価値があります。

何を変更しますか?

-callbacksでattribute_changed?methodを使用している場合は、after_*次のような警告が表示されます。

廃止attribute_changed?予定の警告:コールバック後の内部の動作は、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_changebefore-after値を取得するためにを使用している場合、これも変更され、以下をスローします。

廃止attribute_change予定の警告:コールバック後の内部の動作は、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']。これらにはとしてアクセスできます。


2
この有用な応答には、attribute_wasメソッドの非推奨の回避策も含まれていますsaved_change_to_attribute。代わりに使用してください。
Joe Atzberger

47

before_save代わりにこれを実行できる場合はafter_save、これを使用できます。

self.changed

このレコードで変更されたすべての列の配列を返します。

あなたも使うことができます:

self.changes

変更された列のハッシュと結果の前後を配列として返します


8
これらがafter_コールバックでは機能しないことを除いて、それが実際に問題でした。以下の@jacek-głodekの答えは正しいものです。
ジャズ

これが当てはまることを明確にするために回答を更新しましたbefore_save
mahemoff 2015

1
これはどのRailsバージョンですか?Rails 4ではself.changedafter_saveコールバックで使用できます。
sandre89 2016年

よく働く!また、注意が必要なのself.changedは、文字列の配列です。(記号ではありません!)["attr_name", "other_attr_name"]
LukeS 2017年

8

「選択された」答えは私にはうまくいきませんでした。CouchRest :: Model(アクティブモデルに基づく)でrails 3.1を使用しています。_changed?方法には、変更された属性のためにtrueを返していないafter_updateだけで、フックbefore_updateフック。(new?)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

1
選択した回答もうまくいきませんでしたが、うまくいきました。ありがとう!ActiveRecord 3.2.16を使用しています。
ベン・リー

5

次のafter_updateように条件を追加できます:

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

  ...
end

send_notificationメソッド自体に条件を追加する必要はありません。


-17

変更内容を定義するアクセサーを追加するだけです

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

これは非常に悪い習慣です。考慮しないでください。
Nuno Silva
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.