Come rilevare le modifiche agli attributi dal modello?


86

Vorrei creare una funzione di callback in rails che viene eseguita dopo il salvataggio di un modello.

Ho questo modello, reclamo che ha un attributo "stato" che cambia a seconda dello stato del reclamo, i possibili valori sono in sospeso, approvato, approvato, rifiutato

Il database ha lo "stato" con il valore predefinito "in sospeso".

Vorrei eseguire determinate attività dopo che il modello è stato creato la prima volta o aggiornato da uno stato all'altro, a seconda dello stato da cui cambia.

La mia idea è di avere una funzione nel modello:

    after_save :check_state

    def check_state
      # if status changed from nil to pending (created)
      do this

      # if status changed from pending to approved
      performthistask
     end

La mia domanda è: come posso verificare il valore precedente prima della modifica all'interno del modello?

Risposte:


173

Dovresti guardare ActiveModel :: Modulo sporco : dovresti essere in grado di eseguire le seguenti azioni sul tuo modello di reclamo:

claim.status_changed?  # returns true if 'status' attribute has changed
claim.status_was       # returns the previous value of 'status' attribute
claim.status_change    # => ['old value', 'new value'] returns the old and 
                       # new value for 'status' attribute

claim.name = 'Bob'
claim.changed # => ["name"]
claim.changes # => {"name" => ["Bill", "Bob"]}

Oh! le gioie di Rails!


6
Questo non funzionerà dopo che il modello è stato salvato, che è quello che ha chiesto.
Tom Rossi

4
@TomRossi, le dirtychiamate funzionano in after_save(sia in Rails 2.3 che 3.x). L'ho usato più volte.
Harish Shetty

11
@TomRossi, i flag sporchi vengono ripristinati dopo il commit, quindi non saranno disponibili nei after_commitcallback introdotti in Rails 3.x. Sicuramente funzioneranno after_save.
Harish Shetty

Non ne avevo idea! Pensavo fossero stati ripristinati una volta salvato!
Tom Rossi

5
@TomRossi ho iniziato con lo stesso presupposto qualche anno fa. Quando ho provato a controllare i flag sporchi in after_save ha funzionato. In sostanza, after_saveè un richiamo per uno stato tra after DMLe before_commit. Puoi terminare l'intera transazione after_savelanciando un'eccezione. Se vuoi fare qualcosa dopo il salvataggio senza influenzare l'operazione in corso, usa after_commit:-)
Harish Shetty,

38

puoi usare questo

self.changed

restituisce un array di tutte le colonne che sono state modificate in questo record

puoi anche usare

self.changes

che restituisce un hash di colonne che sono state modificate e prima e dopo i risultati come array


7
Solo una piccola nota per dire che non è necessario usarli self.su questi - puoi semplicemente dire changede changes.
user664833

@ user664833 Più specificamente, puoi omettere selfquando nel modello stesso, ma puoi chiamarli su qualsiasi oggetto con object.changede object.changes. :)
Joshua Pinter

4

Ti consiglio di dare un'occhiata a uno dei plugin disponibili per la macchina a stati:

Entrambi ti permetteranno di impostare stati e transizioni tra stati. Modo molto utile e facile per gestire le tue esigenze.


Sto provando Rubyist-aasm. Diciamo che ho la classe Claim <ActiveRecord :: Base include AASM aasm_column: status aasm_initial_state: pending aasm_state: pending,: enter =>: enter_pending def enter_pending Notifier.deliver_pending_notification (self) end end E il mio campo di stato nel mio database ha il valore predefinito di "in sospeso". Se dovessi fare un Claim.create senza compilare il campo dello stato (in modo che venga eseguito "in sospeso"), AASM eseguirà il metodo "enter_pending"?
David C

2

Per Rails 5.1+, dovresti usare il metodo dell'attributo record attivo: saved_change_to_attribute?

save_change_to_attribute? (nome_attr, ** opzioni) `

Questo attributo è cambiato l'ultima volta che abbiamo salvato? Questo metodo può essere richiamato come saved_change_to_name?invece di saved_change_to_attribute?("name"). Si comporta in modo simile a attribute_changed?. Questo metodo è utile dopo i callback per determinare se la chiamata al salvataggio ha modificato un determinato attributo.

Opzioni

from Una volta passato, questo metodo restituirà false a meno che il valore originale non sia uguale all'opzione data

to Una volta passato, questo metodo restituirà false a meno che il valore non sia stato modificato nel valore specificato

Quindi il tuo modello sarà simile a questo, se vuoi chiamare un metodo basato sulla modifica del valore dell'attributo:

class Claim < ApplicationRecord
  
  after_save :do_this, if: Proc.new { saved_change_to_status?(from: nil, to: 'pending') }

  after_save :do_that, if: Proc.new { saved_change_to_status?(from: 'pending', to: 'approved') }

  
  def do_this
    ..
    ..
  end

  def do_that
    ..
    ..
  end

end

E se non vuoi controllare la modifica del valore nella richiamata, puoi fare quanto segue:

class Claim < ApplicationRecord

  after_save: :do_this, if: saved_change_to_status?


  def do_this
    ..
    ..
  end

end

0

Ho visto la domanda sorgere in molti posti, quindi ho scritto un minuscolo rubygem per questo, per rendere il codice un po 'più carino (ed evitare un milione di dichiarazioni if ​​/ else ovunque): https://github.com/ronna-s / on_change . Spero che aiuti.


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.