Depuis sa version 2.1 (2008 donc), vous pouvez suivre les modifications apportées à vos modèles rails grâce à ActiveModel::Dirty. Mise en application !
En quoi ça consiste ?
Lorsque vous prenez l'un des modèles ActiveRecord de votre application et modifiez un attribut, vous pouvez désirer savoir, lors de la validation des données ou simplement avant l'enregistrement, quels champs ont été modifiés et quelles sont les anciennes et nouvelles valeurs. ActiveModel::Dirty s'occupe de cela pour vous, en suivant les changements dans vos modèles et en les sauvegardant afin d'en faire le suivi par la suite.
Ceci n'est pas un système de contrôle de version. Le suivi n'est sauvegardé nulle part d'autre que dans la RAM. En conséquent à partir du moment ou vous détruisez votre objet, vous perdez le suivi précédent. En revanche, cela permet de vérifier l'ancienne et la nouvelle valeur d'un champs lors de la sauvegarde d'un modèle, ce qui peut être plutôt utile dans certains cas.
Cas concret : Exécuter une action lors de la publication d'un billet sur un blog
Je commence à vraiment en avoir marre de Wordpress et de son code pourri. En conséquent j'ai décidé de commencer à développer mon propre moteur de blog. Cette application tournera sous Rails 3. Ne vous attendez pas à une release avant septembre en revanche.
Dans cette application, je souhaite, lorsque je publie un billet, mettre à jour mon statut Twitter afin de notifier les personnes qui me suivent sur ce réseau social de la présence d'un nouveau contenu sur le blog. Chacun de mes billets a un champ "published" qui est à vrai ou faux. Je ne veux bien évidemment mettre à jour mon status que lorsque je publie le billet. Donc lorsque le champ passe de faux à vrai.
Avec ActiveModel::Dirty, je vais donc pouvoir faire :
class Post < ActiveRecord::Base
validates_presence_of :title, :content, :published
before_save :publish_tweet
private
def publish_tweet
# We don't publish if the post isn't published
return if !published?
# We don't publish if the post was already published before
return if !published_changed?</p>
<p> Resque.enqueue(Jobs::PublishTweet, self.title)
end
end
Que faisons-nous ?
before_save :publish_tweet
Dans cette méthode, nous commençons par filtrer.
return if !published?
return if !published_changed?
Et enfin on publie le tweet.
Resque.enqueue(Jobs::PublishTweet, self.title)
L'API en détail
C'est bien gentil de voir un cas concret mais au final on a pas vu beaucoup de ActiveModel::Dirty ci-dessus. Voyons donc plus en détail les fonctionnalités qu'apporte cette librairie.
Voyons le code suivant tiré de la documentation
person = Person.find_by_name('Bob')
person.changed? # => false
Nous récupérons l'objet de type personne. Vu que nous n'avons encore rien modifié, la méthode changed? retourne faux.
person.name = 'Joe'
person.changed? # => true
person.name_changed? # => true
person.name_was # => 'Bob'
Sur cette même personne, nous modifions son nom. ActiveModel::Dirty nous annonce alors que ce nom a été changé et nous permet d'obtenir la précédente valeur.
person.name_change # => ['Bob', 'Joe']
person.name = 'Bill'
person.name_change # => ['Bob', 'Joe', 'Bill']
Toujours dans le même objet, peut visualiser un tableau contenant toutes les versions de la valeur. Si nous modifions à nouveau ce nom, on constate que name_change contient les 3 valeurs que le nom a porté.
person.save
person.changed? # => false
person.name_changed? # => false
Nous sauvegardons l'objet dans la base. Du coup ActiveModel::Dirty est remis à zéro. On ne constate plus aucun changement.
person.name = 'Bill'
person.name_changed? # => false
person.name_change # => nil
Si nous modifions à nouveau ce nom en lui redonnant la valeur qu'il possède déjà, on ne constate aucun changement en réalité.
person.name = 'Bob'
person.changed # => ['name']
person.changes # => { 'name' => ['Bill', 'Bob'] }
La méthode changed nous retourne la liste de tous les champs ayant été modifiés. Et changes nous retourne un hash contenant toutes les modifications apportées champ par champ.
person.reset_name! # => 'Bill'
person.changed? # => false
person.name_changed? # => false
person.name # => 'Bill'
Enfin on peut annuler un changement. La valeur est restaurée.
Conclusion
Comme vous pouvez le constater, cette API ActiveModel::Dirty peut s'avérer particulièrement puissante et améliorer fortement la qualité de votre code. Vous n'avez plus besoin de tracker les changements apportés à vos modèles par vous même. Rails s'en charge et vous n'avez plus qu'à récupérer les informations.


Commentaires
Les dirty attributes sont très pratiques pour optimiser un peu les applications en évitant par exemple de refaire tout un traitement lourd alors que les "paramètres" de ce traitement n'ont pas changé. En revanche pour pouvoir aller encore plus loin il faudrait vraiment avoir des dirty attributes pour les associations. Ex concret : éviter de recalculer la marge sur une commande si les montants/quantité des positions n'ont pas changé ;-)
Très intéressant comme article ! Sinon c'est pas le propos, mais plutôt que before_save :publish_tweet, un after_save, non ?
Dans l'idéal, oui. Cependant si tu regarde bien l'API, tu verra que l'historique est perdu lorsque tu sauvegarde l'objet. Si on faisait un after_save donc, le status ne serait jamais changé. On ne publierai jamais le tweet.
Bien! Plutôt pratique (voire indispensable) quand on a besoin de notifier - et ce peu importe la manière - les changements opérés sur un objet.