Come determinare se un record è stato appena creato o aggiornato in after_save


95

Il #new_record? funzione determina se un record è stato salvato. Ma è sempre falso nel after_savegancio. C'è un modo per determinare se il record è un record appena creato o uno vecchio dall'aggiornamento?

Spero di non utilizzare un altro callback come before_createimpostare un flag nel modello o richiedere un'altra query nel db.

Ogni consiglio è apprezzato.

Modifica: è necessario determinarlo in after_savehook e, per il mio caso d'uso particolare, non c'è updated_ato updated_ontimestamp


1
hmm forse passare un parametro in un before_save? solo pensando ad alta voce
Trip

Risposte:


167

Stavo cercando di usarlo per una after_saverichiamata.

Una soluzione più semplice è quella di utilizzare id_changed?(poiché non cambierà update) o anche created_at_changed?se sono presenti colonne di timestamp.

Aggiornamento: come sottolinea @mitsy, se questo controllo è necessario al di fuori dei callback, utilizzare id_previously_changed?. Vedi i documenti .



6
È meglio distinguere con after_update e after_create. I callback possono condividere un metodo comune che accetta un argomento per indicare se si tratta di una creazione o di un aggiornamento.
matthuhiggins

2
Questo potrebbe essere cambiato. Almeno in Rails 4, il callback after_save viene eseguito dopo il callback after_create o after_update (vedi guides.rubyonrails.org/active_record_callbacks.html ).
Mark

3
Il controllo di nessuno di questi campi funziona al di fuori di after_save.
fatuhoku

3
id_changed?sarà falso dopo il salvataggio del record (almeno al di fuori degli hook). In tal caso, puoi utilizzareid_previously_changed?
mltsy

30

Nessun binario magico qui che io sappia, dovrai farlo da solo. Puoi ripulirlo usando un attributo virtuale ...

Nella tua classe modello:

def before_save
  @was_a_new_record = new_record?
  return true
end

def after_save
  if @was_a_new_record
    ...
  end
end

26

Ancora un'altra opzione per coloro che fanno hanno un updated_attimestamp:

if created_at == updated_at
  # it's a newly created record
end

Non è una cattiva idea, ma sembra che questo potrebbe ritorcersi contro in alcune situazioni (non necessariamente a prova di proiettile).
Ash Blue il

A seconda di quando viene eseguita una migrazione, i record created_at e updated_at potrebbero essere disattivati. Inoltre corri sempre la possibilità che qualcuno aggiorni un record subito dopo averlo salvato inizialmente, il che potrebbe far perdere tempo alla sincronizzazione. Non è una cattiva idea, sembra solo che potrebbe essere aggiunta un'implementazione più a prova di proiettile.
Ash Blue dal

Inoltre potrebbero essere uguali molto tempo dopo che il record è stato inizialmente creato. Se un record non viene aggiornato per mesi, sembrerà che sia stato appena creato .
bschaeffer

1
@bschaeffer Scusa, la mia domanda era "è possibile created_ateguagliare updated_atuna after_saverichiamata in qualsiasi momento diverso da quando è stata creata per la prima volta?"
colllin

1
@colllin: quando si crea un record, created_ate updated_atsarà uguale in una after_saverichiamata. In tutte le altre situazioni, non saranno uguali nella after_saverichiamata.
bschaeffer

22

C'è una after_createrichiamata che viene chiamata solo se il record è un nuovo record, dopo che è stato salvato . C'è anche una after_updaterichiamata da utilizzare se si trattava di un record esistente che è stato modificato e salvato. La after_saverichiamata viene chiamata in entrambi i casi, dopo la chiamata di after_createo after_update.

Utilizzare after_createse è necessario che accada qualcosa una volta dopo il salvataggio di un nuovo record.

Maggiori informazioni qui: http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html


1
Ehi, grazie per la risposta, questo mi ha aiutato molto. Ciao amico, +10 :)
Adam McArthur

1
Questo potrebbe non essere vero in realtà. Se hai associazioni nella tua creazione, after_create si chiama PRIMA che le associazioni vengano create, quindi se devi assicurarti che TUTTO sia creato, allora devi usare after_save
Niels Kristian

18

Poiché l'oggetto è già stato salvato, dovresti esaminare le modifiche precedenti. L'ID dovrebbe cambiare solo dopo una creazione.

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

C'è anche una variabile di istanza @new_record_before_save. Puoi accedervi procedendo come segue:

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

Entrambi sono piuttosto brutti, ma ti permetterebbero di sapere se l'oggetto è stato appena creato. Spero che aiuti!


Attualmente sono in byebug in una after_savecallback usando Rails 4 e nessuno di questi sta lavorando per identificare questo nuovo record.
MCB

Direi che @object.previous_changes[:id].any?è abbastanza semplice ed elegante. Funziona per me dopo che il record viene aggiornato (non lo chiamo da after_save).
thekingoftruth

1
@object.id_previously_changed?è un po 'meno brutto.
aNoble

@MCB in after_savesu Rails 4 che vorresti guardare changes[:id]invece di previous_changes[:id]. Tuttavia, questo sta cambiando in Rails5.1 (vedere la discussione in github.com/rails/rails/pull/25337 )
gmcnaughton

previous_changes.key?(:id)per una migliore comprensione.
Sebastian Palma,

2

Modalità Rails 5.1+:

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

1

Per Rails 4 (verificato su 4.2.11.1) i risultati changese i previous_changesmetodi sono hash vuoti {}sulla creazione di oggetti all'interno after_save. Quindi attribute_changed?metodi come id_changed?non funzioneranno come previsto.

Ma puoi trarre vantaggio da questa conoscenza e, sapendo che almeno 1 attributo deve essere in changesaggiornamento, controlla se changesè vuoto. Dopo aver confermato che è vuoto, devi essere durante la creazione dell'oggetto:

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

0

Mi piace essere specifico anche se sono consapevole che :idnon dovrebbe cambiare normalmente, ma

(byebug) id_change.first.nil?
true

È sempre più economico essere specifici invece di trovare bug inaspettati molto strani.

Allo stesso modo se mi aspetto una truebandiera da un argomento inaffidabile

def foo?(flag)
  flag == true
end

Ciò consente di risparmiare molte ore per non sedersi su strani bug.

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.