Bestimmen Sie, welche Attribute in Rails after_save callback geändert wurden?


153

Ich richte in meinem Modellbeobachter einen after_save-Rückruf ein, um nur dann eine Benachrichtigung zu senden, wenn das veröffentlichte Attribut des Modells von false in true geändert wurde. Da Methoden wie geändert? sind nur nützlich, bevor das Modell gespeichert wird. Ich versuche dies derzeit (und erfolglos) wie folgt:

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

Hat jemand Vorschläge, wie dies am besten gehandhabt werden kann, vorzugsweise mithilfe von Rückrufen von Modellbeobachtern (um meinen Controller-Code nicht zu verschmutzen)?

Antworten:


182

Schienen 5.1+

Verwendung 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

Oder wenn Sie es vorziehen , saved_change_to_attribute?(:published).

Schienen 3–5.1

Warnung

Dieser Ansatz funktioniert über Rails 5.1 (ist jedoch in 5.1 veraltet und weist in 5.2 wichtige Änderungen auf). Sie können über die Änderung in dieser Pull-Anfrage lesen .

In Ihrem after_updateFilter auf dem Modell , das Sie können verwenden _changed?Accessor. Also zum Beispiel:

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

Es funktioniert einfach.


Vergessen Sie, was ich oben gesagt habe - es funktioniert nicht in Rails 2.0.5. Also eine nützliche Ergänzung zu Rails 3.
Stephen

4
Ich denke, after_update ist jetzt veraltet? Wie auch immer, ich habe es in einem After_Save-Hook versucht und das schien gut zu funktionieren. (Der Änderungen () Hash wurde anscheinend noch nicht in einem after_save zurückgesetzt.)
Tyler Rick

3
Ich musste diese Zeile in die Datei model.rb aufnehmen. Include ActiveModel :: Dirty
coderVishal

13
In späteren Versionen von Rails können Sie dem after_updateAufruf die Bedingung hinzufügen :after_update :send_notification_after_change, if: -> { published_changed? }
Koen.

11
In Rails 5.2 werden einige API-Änderungen vorgenommen. Sie müssen die Änderung während des Rückrufs tun saved_change_to_published?oder saved_change_to_published
abrufen

183

Für diejenigen, die wissen möchten, welche Änderungen gerade in einem after_saveRückruf vorgenommen wurden:

Schienen 5.1 und höher

model.saved_changes

Schienen <5.1

model.previous_changes

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


2
Dies funktioniert perfekt, wenn Sie keine Modellrückrufe verwenden möchten und eine gültige Speicherung benötigen, bevor Sie weitere Funktionen ausführen.
Dave Robertson

Dies ist perfekt! Vielen Dank für die Veröffentlichung.
Jrhicks

Genial! Danke dafür.
Daniel Logan

4
Um ganz klar zu sein: Wenn Sie in meinen Tests (Rails 4) einen after_saveRückruf verwenden, self.changed?ist trueund self.attribute_name_changed?ist dies auch true, gibt aber self.previous_changeseinen leeren Hash zurück.
Sandre89

9
Dies ist in Rails 5.1 + veraltet. Verwenden Sie stattdessen saved_changesin after_saveRückrufen
rico_mac

71

Für alle, die dies später sehen, da es derzeit (August 2017) über Google steht: Es ist erwähnenswert, dass dieses Verhalten in Rails 5.2 geändert wird und ab Rails 5.1 Verfallswarnungen enthält , da sich ActiveModel :: Dirty etwas geändert hat .

Was ändere ich?

Wenn Sie die attribute_changed?Methode in den after_*Rückrufen verwenden, wird eine Warnung wie folgt angezeigt:

DEPRECATION WARNING: Das Verhalten von attribute_changed?Inside-After-Callbacks wird sich in der nächsten Version von Rails ändern. Der neue Rückgabewert spiegelt das Verhalten beim Aufrufen der Methode nach der Rückgabe wider save(z. B. das Gegenteil von dem, was sie jetzt zurückgibt). Verwenden Sie saved_change_to_attribute?stattdessen, um das aktuelle Verhalten beizubehalten . (wird von some_callback unter /PATH_TO/app/models/user.rb:15 aufgerufen)

Wie bereits erwähnt, können Sie dies leicht beheben, indem Sie die Funktion durch ersetzen saved_change_to_attribute?. So zum Beispiel name_changed?wird saved_change_to_name?.

Wenn Sie die verwenden attribute_change, um die Vorher-Nachher-Werte abzurufen, ändert sich dies ebenfalls und löst Folgendes aus:

DEPRECATION WARNING: Das Verhalten von attribute_changeInside-After-Callbacks wird sich in der nächsten Version von Rails ändern. Der neue Rückgabewert spiegelt das Verhalten beim Aufrufen der Methode nach der Rückgabe wider save(z. B. das Gegenteil von dem, was sie jetzt zurückgibt). Verwenden Sie saved_change_to_attributestattdessen, um das aktuelle Verhalten beizubehalten . (wird von some_callback unter /PATH_TO/app/models/user.rb:20 aufgerufen)

Wie bereits erwähnt, ändert die Methode den Namen, zu saved_change_to_attributedem sie zurückkehrt ["old", "new"]. oder use saved_changes, das alle Änderungen zurückgibt und auf die als zugegriffen werden kann saved_changes['attribute'].


2
Beachten Sie, dass diese hilfreiche Antwort auch Problemumgehungen für die Ablehnung von attribute_wasMethoden enthält: Verwenden Sie saved_change_to_attributestattdessen.
Joe Atzberger

47

Wenn Sie dies before_saveanstelle von tun können after_save, können Sie Folgendes verwenden:

self.changed

Es gibt ein Array aller geänderten Spalten in diesem Datensatz zurück.

Sie können auch verwenden:

self.changes

Dies gibt einen Hash von Spalten zurück, die sich geändert haben und vor und nach den Ergebnissen als Arrays


8
Abgesehen davon, dass diese in einem after_Rückruf nicht funktionieren , worum es bei der Frage eigentlich ging. Die Antwort von @ jacek-głodek unten ist die richtige.
Jazz

Die Antwort wurde aktualisiert, um deutlich zu machen, dass dies nur fürbefore_save
Mahemoff

1
Welche Rails-Version ist das? In Rails 4 self.changedkann in after_saveRückrufen verwendet werden.
Sandre89

Funktioniert super! Zu beachten ist auch, dass das Ergebnis self.changedeine Reihe von Zeichenfolgen ist! (Keine Symbole!)["attr_name", "other_attr_name"]
LukeS

8

Die "ausgewählte" Antwort hat bei mir nicht funktioniert. Ich verwende Rails 3.1 mit CouchRest :: Model (basierend auf Active Model). Die _changed?Methoden geben nicht true für geänderte Attribute im after_updateHook zurück, sondern nur im before_updateHook. Ich konnte es mit dem (neuen?) around_updateHaken zum Laufen bringen:

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
Die ausgewählte Antwort hat auch bei mir nicht funktioniert, aber das hat funktioniert. Vielen Dank! Ich verwende ActiveRecord 3.2.16.
Ben Lee

5

Sie können dem after_updateGleichen eine Bedingung hinzufügen, so:

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

  ...
end

Es ist nicht erforderlich, eine Bedingung innerhalb der send_notificationMethode selbst hinzuzufügen .


-17

Sie fügen einfach einen Accessor hinzu, der definiert, was Sie ändern

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

Dies ist eine sehr schlechte Praxis, denken Sie nicht einmal darüber nach.
Nuno Silva
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.