Determinare quali attributi sono stati modificati nella callback di Rails after_save?


153

Sto impostando un callback after_save nel mio osservatore del modello per inviare una notifica solo se l' attributo pubblicato del modello è stato modificato da falso a vero. Da quando sono cambiati metodi come ? sono utili solo prima che il modello venga salvato, il modo in cui attualmente (e senza successo) sto provando a farlo è il seguente:

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

Qualcuno ha qualche suggerimento sul modo migliore di gestirlo, preferibilmente utilizzando callback di osservatore del modello (in modo da non inquinare il mio codice controller)?

Risposte:


182

Rails 5.1+

Utilizzare 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

Oppure, se preferite, saved_change_to_attribute?(:published).

Rotaie 3–5.1

avvertimento

Questo approccio funziona tramite Rails 5.1 (ma è obsoleto in 5.1 e presenta modifiche sostanziali in 5.2). Puoi leggere le modifiche in questa richiesta pull .

Nel tuo after_updatefiltro sul modello puoi usare _changed?accessor. Quindi per esempio:

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

Funziona e basta.


Dimentica quello che ho detto sopra: NON funziona in Rails 2.0.5. Quindi un'utile aggiunta a Rails 3.
stephenr,

4
Penso che after_update sia obsoleto adesso? Ad ogni modo, l'ho provato con un hook after_save e sembrava funzionare bene. (A quanto pare, l'hash di change () non è ancora stato ripristinato in un after_save.)
Tyler Rick

3
Ho dovuto includere questa riga nel file model.rb. include ActiveModel :: Dirty
coderVishal

13
Nelle versioni successive di Rails è possibile aggiungere la condizione alla after_updatechiamata:after_update :send_notification_after_change, if: -> { published_changed? }
Koen.

11
Ci saranno alcune modifiche all'API in Rails 5.2. Dovrai fare saved_change_to_published?o saved_change_to_publishedrecuperare la modifica durante il callback
alopez02

183

Per coloro che vogliono conoscere le modifiche appena apportate in un after_savecallback:

Rotaie 5.1 e successive

model.saved_changes

Rotaie <5.1

model.previous_changes

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


2
Funziona perfettamente quando non si desidera utilizzare i callback del modello e è necessario un salvataggio valido prima di eseguire ulteriori funzionalità.
Dave Robertson,

Questo è perfetto! grazie per aver postato questo.
jrhicks,

Eccezionale! Grazie per questo.
Daniel Logan,

4
Giusto per essere chiari: nei miei test (Rails 4), se si utilizza un after_savecallback, self.changed?è trueed self.attribute_name_changed?è anche true, ma self.previous_changesrestituisce un hash vuoto.
sandre89,

9
Questo è deprecato in Rails 5.1 +. Utilizzare invece saved_changesnei after_savecallback
rico_mac il

71

A chiunque lo vedrà in seguito, poiché attualmente (agosto 2017) supera Google: vale la pena ricordare che questo comportamento verrà modificato in Rails 5.2 e che presenta avvisi di deprecazione a partire da Rails 5.1, poiché ActiveModel :: Dirty è cambiato un po ' .

Cosa cambio?

Se stai usando il attribute_changed?metodo in after_*-callbacks, vedrai un avviso come:

AVVISO DI DEPRECAZIONE: il comportamento di attribute_changed?inside of after callbacks cambierà nella prossima versione di Rails. Il nuovo valore restituito rifletterà il comportamento di chiamare il metodo dopo saverestituito (ad es. L'opposto di ciò che restituisce ora). Per mantenere il comportamento corrente, utilizzare saved_change_to_attribute?invece. (chiamato da some_callback su /PATH_TO/app/models/user.rb:15)

Come accennato, è possibile risolvere facilmente sostituendo la funzione con saved_change_to_attribute?. Quindi, ad esempio, name_changed?diventa saved_change_to_name?.

Allo stesso modo, se si utilizza il attribute_changeper ottenere i valori prima e dopo, anche questo cambia e genera quanto segue:

AVVISO DI DEPRECAZIONE: il comportamento di attribute_changeinside of after callbacks cambierà nella prossima versione di Rails. Il nuovo valore restituito rifletterà il comportamento di chiamare il metodo dopo saverestituito (ad es. L'opposto di ciò che restituisce ora). Per mantenere il comportamento corrente, utilizzare saved_change_to_attributeinvece. (chiamato da some_callback su /PATH_TO/app/models/user.rb:20)

Ancora una volta, come menziona, il metodo cambia nome a saved_change_to_attributecui ritorna ["old", "new"]. o utilizzare saved_changes, che restituisce tutte le modifiche e alle quali è possibile accedere come saved_changes['attribute'].


2
Nota che questa utile risposta include anche soluzioni alternative per la deprecazione dei attribute_wasmetodi: usa saved_change_to_attributeinvece.
Joe Atzberger,

47

Nel caso in cui tu possa fare questo before_saveinvece di after_save, sarai in grado di usare questo:

self.changed

restituisce un array di tutte le colonne modificate in questo record.

puoi anche usare:

self.changes

che restituisce un hash di colonne modificate e risultati prima e dopo come matrici


8
Solo che questi non funzionano in un after_callback, che è in realtà la domanda. La risposta di @ jacek-głodek qui sotto è quella corretta.
Jazz,

Aggiornato la risposta per chiarire che questo vale solo perbefore_save
mahemoff

1
Quale versione di Rails è questa? In Rails 4, self.changedpuò essere utilizzato nei after_savecallback.
sandre89,

Funziona alla grande! Inoltre, il risultato di self.changedè una serie di stringhe! (Non simboli!)["attr_name", "other_attr_name"]
Luca

8

La risposta "selezionata" non ha funzionato per me. Sto usando le rotaie 3.1 con CouchRest :: Model (basato su Active Model). I _changed?metodi non restituiscono true per gli attributi modificati nel after_updatehook, solo nel before_updatehook. Sono stato in grado di farlo funzionare utilizzando il around_updategancio (nuovo?) :

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
Anche la risposta selezionata non ha funzionato per me, ma ha funzionato. Grazie! Sto usando ActiveRecord 3.2.16.
Ben Lee,

5

puoi aggiungere una condizione al after_updatesimile così:

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

  ...
end

non è necessario aggiungere una condizione all'interno del send_notificationmetodo stesso.


-17

Devi solo aggiungere un accessorio che definisca ciò che cambi

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

Questa è una brutta pratica, non considerarla nemmeno.
Nuno Silva,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.