Quand est-il acceptable de lever une exception ActiveRecord::IrreversibleMigration dans la méthode self.down d'une migration ? Quand faut-il prendre la peine d'implémenter l'inverse de la migration ?
Réponses
Trop de publicités?Si vous avez affaire à des systèmes de production alors oui, c'est très mauvais. S'il s'agit de votre propre projet, alors tout est permis (si rien d'autre, ce sera une expérience d'apprentissage :) bien qu'il y ait de fortes chances que tôt ou tard, même dans le cadre d'un projet, vous vous retrouviez à avoir mis une croix sur une migration inverse pour devoir annuler cette migration quelques jours plus tard, que ce soit via rake
ou manuellement).
Dans un scénario de production, vous devez toujours faire l'effort d'écrire et de tester une migration réversible dans l'éventualité où vous le passez en production, puis découvrez un bug qui vous oblige à revenir en arrière (code y schéma) à une révision antérieure (en attendant un correctif non trivial -- et un système de production autrement inutilisable).
Les migrations inversées peuvent être triviales (suppression des colonnes ou des tables qui ont été ajoutées pendant la migration, et/ou modification des types de colonnes, etc.) ou un peu plus complexes ( execute
de JOIN
ed INSERT
ou UPDATE
), mais rien n'est complexe au point de justifier de le "balayer sous le tapis". Au moins, le fait de vous forcer à réfléchir à des moyens de réaliser des migrations inverses peut vous donner un nouvel aperçu du problème que votre migration vers l'avant est en train de résoudre.
Vous pouvez parfois vous trouver dans une situation où une migration vers l'avant supprime une fonctionnalité, ce qui entraîne l'élimination de données de la base de données. Pour des raisons évidentes, la migration inverse ne peut pas ressusciter les données supprimées. Bien que l'on puisse, dans de tels cas, recommander que la migration vers l'avant sauvegarde automatiquement les données ou les conserve dans l'éventualité d'un retour en arrière comme alternative à un échec pur et simple (sauvegarder vers yml
), vous n'êtes pas obligé de le faire, car le temps nécessaire pour tester une telle procédure automatisée pourrait dépasser le temps nécessaire pour restaurer les données manuellement (si le besoin s'en fait sentir). Mais même dans de tels cas, au lieu de simplement échouer vous pouvez toujours faire la migration inverse conditionnellement et temporairement échouer dans l'attente d'une action de l'utilisateur (c'est-à-dire tester l'existence d'une table requise qui doit être restaurée manuellement ; si elle est absente, le message "J'ai échoué car je ne peux pas recréer la table XYZ
du néant ; restaurer manuellement la table XYZ
de la sauvegarde, alors courez-moi à nouveau, et je ne vous laisserai pas tomber !")
Si vous détruisez des données, vous pouvez d'abord en faire une sauvegarde. Par exemple.
def self.up
# create a backup table before destroying data
execute %Q[create table backup_users select * from users]
remove_column :users, :timezone
end
def self.down
add_column :users, :timezone, :string
execute %Q[update users U left join backup_users B on (B.id=U.id) set U.timezone = B.timezone]
execute %Q[drop table backup_users]
end
Dans un scénario de production, vous devriez toujours faire l'effort d'écrire et de tester une migration réversible dans l'éventualité où vous l'effectuez en production, puis découvrez un bogue qui vous oblige à revenir (code et schéma) à une révision antérieure (dans l'attente d'un correctif non trivial - et d'un système de production inutilisable).
La migration réversible est une bonne chose pour le développement et la mise en place, mais si le code est bien testé, il devrait être extrêmement rare que vous souhaitiez effectuer une migration vers le bas en production. J'intègre dans mes migrations une IrreversibleMigration automatique en mode production. Si j'avais vraiment besoin d'inverser une modification, je pourrais utiliser une autre migration "up" ou supprimer l'exception. Mais cela semble peu convaincant. Tout bogue susceptible de provoquer un scénario aussi désastreux est un signe que le processus d'assurance qualité est sérieusement déréglé.
Avoir l'impression d'avoir besoin d'une migration irréversible est probablement un signe que vous avez de plus gros problèmes qui se profilent. Peut-être que des détails précis pourraient aider ?
Quant à votre deuxième question : Je fais toujours l'effort d'écrire l'inverse des migrations. Bien sûr, I n'écrivent pas réellement le .down
TextMate l'insère automatiquement lors de la création de l'étiquette. .up
.
Migration réversible des données permet de créer facilement des migrations de données réversibles à l'aide de fichiers yaml.
class RemoveStateFromProduct < ActiveRecord::Migration
def self.up
backup_data = []
Product.all.each do |product|
backup_data << {:id => product.id, :state => product.state}
end
backup backup_data
remove_column :products, :state
end
def self.down
add_column :products, :state, :string
restore Product
end
end