Garder une trace de tous les changements avec Audited

Publié le 17 octobre 2017 par Numa Claudel | outils

Cet article est publié sous licence CC BY-NC-SA

Aujourd’hui je vais vous présenter une gem permettant de garder une trace des actions réalisées dans une application Rails de manière simple. Cette gem se nomme Audited et permet d’historiser les modifications sur nos modèles Rails avec seulement quelques lignes de code.

De plus elle permet de consulter les données enregistrées par le biais de méthodes utilisables directement sur les modèles, et laisse quelques libertés de configuration afin d’affiner les informations ainsi sauvegardées.

Enregistrer les actions

Installez la gem :

# Gemfile

gem "audited", "~> 4.5"
$ rails generate audited:install
$ rake db:migrate

Ceci va générer une migration et créer une table audits.

C’est à partir de maintenant que l’on fait le choix des informations que l’on veut enregistrer.

Pour commencer, enregistrer toutes les actions effectuées sur un modèle est aussi simple que d’ajouter l’instruction audited sur celui-ci. À cela s’ajoute des options permettant de restreindre le scope d’enregistrement, ainsi que l’ajout d’informations :

  • audited only: [], audited except: [] offrent la possibilité de restreindre le déclenchement de l’enregistrement seulement si certains attributs sont modifiés ;
  • audited on: [] limite les actions déclenchant un enregistrement.

Pour compléter les informations enregistrées, par exemple pour donner des précisions sur le contexte, il est possible d’ajouter des commentaires :

@product.update_attributes(
    quantity: 3,
    audit_comment: "Changement depuis le compte client"
)

Dans le même genre, si l’on veut conserver les informations d’un modèle associé au moment de l’action :

class Product < ActiveRecord::Base
  belongs_to :category
  audited associated_with: :category

class Category < ActiveRecord::Base
  has_many :products
  has_associated_audits

Pour ce qui est de l’auteur de l’action enregistrée, Audited utilise la méthode current_user des contrôleurs. Si elle n’existe pas, il faudra donc soit l’ajouter, soit préciser à Audited quelle méthode utiliser :

# config/initializers/autdited.rb
Audited.current_user_method = :authenticated_user

Consultation / utilisation

Pour accéder aux informations enregistrées, il est possible de les consulter au moyen de méthodes accessibles sur les modèles tagués audited :

product = Product.create(quantity: 2)
product.update_attributes!(quantity: 3)
product.destroy
product.audits.count # => 3

# informations associées
category = Category.create(name: "Basket")
product = category.products.create(quantity: 3)
product.update_attribute(quantity: 2)
product.audits.last.associated # => #<Category name: "Basket">
company.associated_audits.last.auditable # => #<Product quantity: 2>

Audited assigne un numéro de version aux informations enregistrées. Il est donc possible d’accéder à une liste de versions, ou bien d’en consulter une grâce à son numéro ou en utilisant une date :

product.revisions
product.revision(1)
product.revision_at(Date.new(2017, 10, 17))

D’ailleurs voici la structure de base d’un enregistrement Audited :

<Audited::Audit
  id: 1,
  auditable_id: 16,
  auditable_type: "Product",
  associated_id: nil,
  associated_type: nil,
  user_id: 1,
  user_type: "User",
  username: nil,
  action: "update",
  audited_changes: {"description"=>["bla", "bla bla"]},
  version: 1,
  comment: nil,
  remote_address: "0:0:0:0:0:0:0:1",
  request_uuid: "744350ed-fb09-46c0-95ff-ecf883415e81",
  created_at: "2017-10-09 14:32:13"
>

L’enregistrement est composé :

  • d’un id
  • de la référence à l’objet sur lequel l’action s’est produite
  • de l’objet associé (si l’information a été demandée)
  • de la référence de l’utilisateur à l’origine de l’action (current_user)
  • du type d’action
  • de la liste des changements opérés
  • du numéro de version
  • des commentaires que l’on a pu ajouter
  • de l’adresse d’émission de la requête (action)
  • de l’id unique de la requête attribuée par Audited et d’une date de création

Au fait, si l’on veut effectuer des actions sans que celles-ci soit enregistrées (sur un modèle sur lequel on veut normalement enregistrer les actions) ? … Eh bien c’est tout à fait possible :

@product.save_without_auditing

# La version block
@product.without_auditing do
  @product.save
end

# Désactiver l'observation de colonnes
Product.non_audited_columns = [:quantity]

# Désactiver l'observation sur un modèle
product.auditing_enabled = false

On peut aussi passer directement par le modèle Audited::Audit, qui est un modèle ActiveRecord comme les autres. À ce sujet, il est possible d’étendre ce modèle pour le personnaliser et y intégrer de nouvelles méthodes ou requêtes. Pour cela, il faut créer un modèle héritant de la classe Audited::Audit :

class CustomAudit < Audited::Audit

  scope :custom_scope, -> { where() }

  def custom_method
    "Hey!"
  end

end

Et initialiser Audited avec ce modèle personnalisé :

# config/initializers/audited.rb

Audited.config do |config|
  config.audit_class = CustomAudit
end

Conclusion

Audited peut être utile à plusieurs fins :

  • conserver une trace des actions utilisateur, on peut voir ça comme un genre de git blame, mais sur les données
  • on peut aussi faire des statistiques avec toutes ces données enregistrées
  • en cas de débogage / assistance en production, on peut aussi déceler facilement si un bug est bel et bien un bug ou provient d’une action volontaire

Il faut tout de même veiller à la taille de la table audits, qui peut rapidement faire un poids important si d’aventure on décidait d’enregistrer toutes les actions de l’application.


L’équipe Synbioz.
Libres d’être ensemble.