So stellen Sie fest, ob ein Datensatz gerade in after_save erstellt oder aktualisiert wurde

95

Der #new_record? Funktion bestimmt, ob ein Datensatz gespeichert wurde. Aber es ist immer falsch im after_saveHaken. Gibt es eine Möglichkeit, anhand der Aktualisierung festzustellen, ob es sich bei dem Datensatz um einen neu erstellten oder einen alten Datensatz handelt?

Ich hoffe, keinen weiteren Rückruf before_createzu verwenden, um beispielsweise ein Flag im Modell zu setzen oder eine weitere Abfrage in der Datenbank zu erfordern.

Jeder Rat wird geschätzt.

Bearbeiten: Sie müssen es im after_saveHook bestimmen , und für meinen speziellen Anwendungsfall gibt es keinen updated_atoder updated_onZeitstempel

Aaron Qian
quelle
1
hmm vielleicht einen param in einem before_save übergeben? nur laut denken
Trip

Antworten:

167

Ich wollte dies für einen after_saveRückruf verwenden.

Eine einfachere Lösung ist die Verwendung id_changed?(da sie sich nicht ändert update) oder auch created_at_changed?wenn Zeitstempelspalten vorhanden sind.

Update: Wie @mitsy hervorhebt, verwenden Sie diese Option, wenn diese Prüfung außerhalb von Rückrufen erforderlich ist id_previously_changed?. Siehe Dokumente .

Zubin
quelle
3
über ActiveModel :: Dirty
Chaserx
6
Es ist am besten, zwischen einem after_update und einem after_create zu unterscheiden. Die Rückrufe können eine gemeinsame Methode verwenden, die ein Argument verwendet, um anzugeben, ob es sich um eine Erstellung oder Aktualisierung handelt.
Matthuhiggins
2
Dies könnte sich geändert haben. Zumindest in Rails 4 wird der Rückruf after_save nach dem Rückruf after_create oder after_update ausgeführt (siehe guide.rubyonrails.org/active_record_callbacks.html ).
Mark
3
Wenn Sie überprüfen, ob keines dieser Felder außerhalb von funktioniert after_save.
Fatuhoku
3
id_changed?wird nach dem Speichern des Datensatzes falsch sein (zumindest außerhalb der Hooks). In diesem Fall können Sieid_previously_changed?
mltsy
30

Keine Schienenmagie hier, von der ich weiß, du musst es selbst tun. Sie können dies mit einem virtuellen Attribut bereinigen ...

In Ihrer Modellklasse:

def before_save
  @was_a_new_record = new_record?
  return true
end

def after_save
  if @was_a_new_record
    ...
  end
end
Jeff Paquette
quelle
26

Eine weitere Option für diejenigen , die zu tun haben einen updated_atZeitstempel:

if created_at == updated_at
  # it's a newly created record
end
Colllin
quelle
Keine schlechte Idee, aber es scheint, dass dies in einigen Situationen nach hinten losgehen könnte (nicht unbedingt kugelsicher).
Ash Blue
Abhängig davon, wann eine Migration ausgeführt wird, sind die Datensätze created_at und updated_at möglicherweise deaktiviert. Außerdem besteht immer die Möglichkeit, dass jemand einen Datensatz direkt nach dem ersten Speichern aktualisiert, wodurch die Zeit möglicherweise nicht mehr synchron ist. Es ist keine schlechte Idee, es scheint nur, dass eine kugelsichere Implementierung hinzugefügt werden könnte.
Ash Blue
Sie können auch lange nach der ersten Erstellung des Datensatzes gleich sein. Wenn ein Datensatz monatelang nicht aktualisiert wird, sieht es so aus, als wäre er gerade erstellt worden .
Bschaeffer
1
@bschaeffer Entschuldigung, meine Frage war: "Ist es möglich created_at, updated_ateinen after_saveRückruf zu einem anderen Zeitpunkt als dem Zeitpunkt seiner ersten Erstellung gleichzustellen ?"
Colllin
1
@colllin: Beim Erstellen eines Datensatzes created_atund updated_atwird in einem after_saveRückruf gleich sein. In allen anderen Situationen sind sie im after_saveRückruf nicht gleich .
Bschaeffer
22

Es gibt einen after_createRückruf, der nur aufgerufen wird, wenn der Datensatz nach dem Speichern ein neuer Datensatz ist . Es gibt auch einen after_updateRückruf zur Verwendung, wenn dies ein vorhandener Datensatz war, der geändert und gespeichert wurde. Der after_saveRückruf wird in beiden Fällen entweder nach after_createoder after_updateaufgerufen.

Verwenden after_createSie diese Option, wenn nach dem Speichern eines neuen Datensatzes einmal etwas passieren muss.

Weitere Informationen hier: http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html

Scharfsinn
quelle
1
Hey, danke für die Antwort, das hat mir sehr geholfen. Prost Mann, +10 :)
Adam McArthur
1
Dies könnte tatsächlich nicht wahr sein. Wenn Sie Assoziationen in Ihrer Erstellung haben, wird after_create BEVOR die Assoziationen erstellt werden. Wenn Sie also sicherstellen müssen, dass ALLES erstellt wird, müssen Sie after_save
Niels Kristian
18

Da das Objekt bereits gespeichert wurde, müssen Sie sich die vorherigen Änderungen ansehen. Die ID sollte sich erst nach einer Erstellung ändern.

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

Es gibt auch eine Instanzvariable @new_record_before_save. Sie können darauf zugreifen, indem Sie folgende Schritte ausführen:

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

Beide sind ziemlich hässlich, aber Sie können feststellen, ob das Objekt neu erstellt wurde. Hoffentlich hilft das!

Tom Rossi
quelle
Ich bin derzeit in einem after_saveRückruf mit Rails 4 in Byebug und keiner von beiden arbeitet daran, diesen neuen Datensatz zu identifizieren.
MCB
Ich würde sagen, das @object.previous_changes[:id].any?ist ganz einfach und elegant. Es funktioniert für mich, nachdem der Datensatz aktualisiert wurde (ich rufe ihn nicht an after_save).
thekingoftruth
1
@object.id_previously_changed?ist ein bisschen weniger hässlich.
Adel
@MCB in after_saveauf Rails 4, die Sie sich changes[:id]stattdessen ansehen möchten previous_changes[:id]. Dies ändert sich jedoch in Rails5.1 (siehe Diskussion in github.com/rails/rails/pull/25337 )
gmcnaughton
previous_changes.key?(:id)zum besseren Verständnis.
Sebastian Palma
2

Schienen 5.1+ Weg:

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

Für Rails 4 (aktiviert in 4.2.11.1) sind Ergebnisse changesund previous_changesMethoden leere Hashes {}bei der Objekterstellung im Inneren after_save. So attribute_changed?Methoden wie id_changed?nicht funktionieren wie erwartet.

Sie können dieses Wissen jedoch nutzen und - wenn Sie wissen, dass mindestens 1 Attribut changesbeim Update vorhanden sein muss - prüfen, ob changeses leer ist. Sobald Sie bestätigt haben, dass es leer ist, müssen Sie sich während der Objekterstellung befinden:

after_save do
  if changes.empty?
    # code appropriate for object creation goes here ...
  end
end
Kryptogopher
quelle
0

Ich bin gerne spezifisch, auch wenn ich mir bewusst bin, dass sich :iddas nicht normal ändern sollte, aber

(byebug) id_change.first.nil?
true

Es ist immer billiger, spezifisch zu sein, anstatt einen sehr seltsamen, unerwarteten Fehler zu finden.

Genauso, wenn ich eine trueFlagge von einem nicht vertrauenswürdigen Argument erwarte

def foo?(flag)
  flag == true
end

Dies spart viele Stunden, um nicht auf seltsamen Käfern zu sitzen.

x'ES
quelle