Comment déterminer si un enregistrement vient d'être créé ou mis à jour dans after_save


95

Le #new_record? La fonction détermine si un enregistrement a été sauvegardé. Mais c'est toujours faux dans le after_savecrochet. Existe-t-il un moyen de déterminer si l'enregistrement est un enregistrement nouvellement créé ou un ancien à partir de la mise à jour?

J'espère ne pas utiliser un autre rappel tel que before_createdéfinir un indicateur dans le modèle ou exiger une autre requête dans la base de données.

Tout conseil est apprécié.

Edit: Nécessité de déterminer dans after_savecrochet, et pour mon cas particulier d'utilisation, il n'y a pas updated_atou updated_onhorodatage


1
hmm peut-être passer un paramètre dans un before_save? penser à voix haute
Voyage du

Réponses:


168

Je cherchais à l'utiliser pour un after_saverappel.

Une solution plus simple consiste à utiliser id_changed?(car cela ne changera pas update) ou même created_at_changed?si des colonnes d'horodatage sont présentes.

Mise à jour: comme le souligne @mitsy, si cette vérification est nécessaire en dehors des rappels, utilisez id_previously_changed?. Voir la documentation .



6
Il est préférable de différencier avec un after_update et un after_create. Les rappels peuvent partager une méthode commune qui prend un argument pour indiquer s'il s'agit d'une création ou d'une mise à jour.
matthuhiggins

2
Cela a peut-être changé. Au moins dans Rails 4, le rappel after_save s'exécute après le rappel after_create ou after_update (voir guides.rubyonrails.org/active_record_callbacks.html ).
Mark

3
Vérifier qu'aucun de ces champs ne fonctionne en dehors de after_save.
fatuhoku

3
id_changed?sera faux après l'enregistrement de l'enregistrement (au moins en dehors des hooks). Dans ce cas, vous pouvez utiliserid_previously_changed?
mltsy

31

Pas de magie des rails ici que je sache, vous devrez le faire vous-même. Vous pouvez nettoyer cela en utilisant un attribut virtuel ...

Dans votre classe de modèle:

def before_save
  @was_a_new_record = new_record?
  return true
end

def after_save
  if @was_a_new_record
    ...
  end
end

26

Une autre option, pour ceux qui n'ont un horodatage:updated_at

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

Ce n'est pas une mauvaise idée, mais il semble que cela pourrait se retourner contre vous dans certaines situations (pas nécessairement à l'épreuve des balles).
Ash Blue

Selon le moment où une migration est exécutée, les enregistrements created_at et updated_at peuvent être désactivés. De plus, vous courez toujours la chance que quelqu'un mette à jour un enregistrement juste après l'avoir initialement sauvegardé, ce qui pourrait désynchroniser l'heure. Ce n'est pas une mauvaise idée, on dirait qu'une implémentation plus à l'épreuve des balles pourrait être ajoutée.
Ash Blue

Ils peuvent également être égaux longtemps après la création initiale de l'enregistrement. Si un enregistrement n'est pas mis à jour pendant des mois, il semblera qu'il vient d'être créé .
bschaeffer

1
@bschaeffer Désolé, ma question était "est-il possible created_atd'égaler updated_atdans un after_saverappel à tout moment autre que lors de sa création?"
colllin

1
@colllin: lors de la création d'un enregistrement, created_atet updated_atsera égal dans un after_saverappel. Dans toutes les autres situations, ils ne seront pas égaux dans le after_saverappel.
bschaeffer

22

Il y a un after_createrappel qui n'est appelé que si l'enregistrement est un nouvel enregistrement, après sa sauvegarde . Il existe également un after_updaterappel à utiliser s'il s'agissait d'un enregistrement existant qui a été modifié et enregistré. Le after_saverappel est appelé dans les deux cas, après l'un after_createou l' autre after_update.

À utiliser after_createsi vous avez besoin que quelque chose se produise une fois après l'enregistrement d'un nouvel enregistrement.

Plus d'informations ici: http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html


1
Hé, merci pour la réponse, cela m'a beaucoup aidé. Cheers man, +10 :)
Adam McArthur

1
Cela pourrait ne pas être vrai en fait. Si vous avez des associations dans votre création, alors after_create est appelé AVANT que les associations ne soient créées, donc si vous devez vous assurer que TOUT est créé, vous devez utiliser after_save
Niels Kristian

18

Étant donné que l'objet a déjà été enregistré, vous devez examiner les modifications précédentes. L'ID ne doit changer qu'après une création.

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

Il existe également une variable d'instance @new_record_before_save. Vous pouvez y accéder en procédant comme suit:

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

Les deux sont assez laids, mais ils vous permettraient de savoir si l'objet a été nouvellement créé. J'espère que cela pourra aider!


Je suis actuellement dans byebug dans un after_saverappel utilisant Rails 4 et aucun d'eux ne travaille pour identifier ce nouvel enregistrement.
MCB

Je dirais que @object.previous_changes[:id].any?c'est assez simple et élégant. Cela fonctionne pour moi une fois l'enregistrement mis à jour (je ne l'appelle pas depuis after_save).
thekingoftruth

1
@object.id_previously_changed?est un peu moins moche.
aNoble

@MCB dans after_saveRails 4 que vous voudriez regarder au changes[:id]lieu de previous_changes[:id]. Cependant, cela change dans Rails5.1 (voir la discussion sur github.com/rails/rails/pull/25337 )
gmcnaughton

previous_changes.key?(:id)pour une meilleure compréhension.
Sebastian Palma le

2

Rails 5.1+ façon:

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

1

Pour Rails 4 (vérifié sur 4.2.11.1), les résultats de changeset les previous_changesméthodes sont des hachages vides {}lors de la création d'objets à l'intérieur after_save. Ainsi, des attribute_changed?méthodes comme celles- id_changed?ci ne fonctionneront pas comme prévu.

Mais vous pouvez profiter de cette connaissance et - sachant qu'au moins 1 attribut doit être présent lors changesde la mise à jour - vérifiez s'il changesest vide. Une fois que vous confirmez qu'il est vide, vous devez être lors de la création de l'objet:

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

0

J'aime être précis même si je suis conscient que cela :idne devrait pas changer normalement, mais

(byebug) id_change.first.nil?
true

Il est toujours moins cher d'être précis au lieu de trouver un bug inattendu très étrange.

De la même manière si j'attends le truedrapeau d'un argument non fiable

def foo?(flag)
  flag == true
end

Cela permet de gagner beaucoup d'heures pour ne pas rester assis sur des bugs étranges.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.