web-dev-qa-db-fra.com

Déterminez quels attributs ont été modifiés dans Rails after_save callback?

Je configure un rappel after_save dans mon observateur de modèle pour envoyer une notification uniquement si l'attribut publié du modèle est passé de false à true. Comme les méthodes telles que modifié? ne sont utiles que avant l'enregistrement du modèle, voici comment j'essaie (et sans succès) de le faire:

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

Quelqu'un a-t-il des suggestions quant à la meilleure façon de gérer cela, en utilisant de préférence des rappels d'observateurs de modèle (afin de ne pas polluer le code de mon contrôleur)?

135
modulaaron

Dans votre after_update filtre sur le modèle que vous pouvez utiliser _changed? accesseur (au moins dans Rails 3, pas sûr de Rails 2)]. Ainsi, par exemple:

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

Ça fonctionne.

168
Radek Paviensky

Pour ceux qui veulent connaître les modifications apportées à un after_save rappeler:

Rails 5.1 et plus

model.saved_changes

Rails <5.1

model.previous_changes

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

161
Jacek Głodek

À tous ceux qui le verront plus tard, comme il le fait actuellement (août 2017) en tête de Google: il est intéressant de noter que ce comportement sera modifié dans Rails 5.2 , et a des avertissements de dépréciation à partir de Rails 5.1, comme ActiveModel :: Dirty changé un peu.

Qu'est-ce que je change?

Si vous utilisez attribute_changed? méthode dans le after_*- callbacks, vous verrez un avertissement comme:

AVERTISSEMENT DE DEPRECATION: Le comportement de attribute_changed? à l'intérieur de après les rappels seront modifiés dans la prochaine version de Rails. La nouvelle valeur de retour reflétera le comportement de l'appel de la méthode après le retour de save (par exemple, l'opposé de ce qu'elle retourne maintenant). Pour conserver le comportement actuel, utilisez saved_change_to_attribute? au lieu. (appelé de some_callback à /PATH_TO/app/models/user.rb:15)

Comme il est mentionné, vous pouvez résoudre ce problème facilement en remplaçant la fonction par saved_change_to_attribute?. Ainsi, par exemple, name_changed? devient saved_change_to_name?.

De même, si vous utilisez le attribute_change pour obtenir les valeurs avant-après, cela change également et génère ce qui suit:

AVERTISSEMENT DE DEPRECATION: Le comportement de attribute_change à l'intérieur de après les rappels seront modifiés dans la prochaine version de Rails. La nouvelle valeur de retour reflétera le comportement de l'appel de la méthode après le retour de save (par exemple, l'opposé de ce qu'elle retourne maintenant). Pour conserver le comportement actuel, utilisez saved_change_to_attribute au lieu. (appelé de some_callback à /PATH_TO/app/models/user.rb:20)

De nouveau, comme elle le mentionne, la méthode change de nom et devient saved_change_to_attribute qui retourne ["old", "new"]. Ou utiliser saved_changes, qui renvoie toutes les modifications, accessibles en tant que saved_changes['attribute'].

55
Frederik Spang

Si vous pouvez le faire sur before_save au lieu de after_save, vous pourrez utiliser ceci:

self.changed

il retourne un tableau de toutes les colonnes modifiées dans cet enregistrement.

vous pouvez aussi utiliser:

self.changes

qui retourne un hachage de colonnes qui ont changé et avant et après les résultats sous forme de tableaux

46
zeacuss

La réponse "sélectionnée" n'a pas fonctionné pour moi. J'utilise Rails 3.1 avec CouchRest :: Model (basé sur le modèle actif). Les méthodes _changed? Ne renvoient pas la valeur true pour les attributs modifiés dans le paramètre after_update crochet, seulement dans le crochet before_update. J'ai réussi à le faire fonctionner avec le crochet (nouveau?) around_update:

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
7
Jeff Gran

vous pouvez ajouter une condition à la after_update ainsi:

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

  ...
end

il n'est pas nécessaire d'ajouter une condition dans le send_notification méthode elle-même.

4
echo

J'utilise ceci pour extraire un hachage avec les nouvelles valeurs d'attribut, ce qui m'est utile pour mettre à jour d'autres modèles

attributes_changed = self.changes.inject(Hash.new){|hash,attr| ((hash[attr[0].to_sym] = attr[1].last) || attr[1].last == false) && hash}

Le

attr[1].last == false

est nécessaire lorsque la nouvelle valeur est false, où l'affectation renvoie false et "hash" non renvoyé.

Je suppose qu'il y a un moyen plus facile, je suis nouveau sur Rails

0
Maragues