J'obtiens l'erreur suivante en essayant d'ajouter une colonne NOT NULL à une table existante. Pourquoi cela se produit-il?. J'ai essayé de rake db: reset en pensant que les enregistrements existants sont le problème, mais même après la réinitialisation de la base de données, le problème persiste. Pouvez-vous m'aider à comprendre cela.
Fichier de migration
class AddDivisionIdToProfile < ActiveRecord::Migration
def self.up
add_column :profiles, :division_id, :integer, :null => false
end
def self.down
remove_column :profiles, :division_id
end
end
Message d'erreur
SQLite3 :: SQLException: Impossible d'ajouter une colonne NOT NULL avec la valeur par défaut NULL: ALTER TABLE "profiles" ADD "division_id" integer NOT NULL
Vous avez déjà des lignes dans le tableau et vous ajoutez une nouvelle colonne division_id
. Il a besoin de quelque chose dans cette nouvelle colonne dans chacune des lignes existantes.
SQLite choisit généralement NULL, mais vous avez spécifié qu'il ne peut pas être NULL, alors que devrait-il être? Il n'a aucun moyen de le savoir.
Voir:
La recommandation de ce blog est d'ajouter la colonne sans la contrainte non nulle, et elle sera ajoutée avec NULL dans chaque ligne. Ensuite, vous pouvez remplir des valeurs dans le division_id
puis utilisez change_column
pour ajouter la contrainte non nulle.
Consultez les blogs auxquels j'ai lié pour une description d'un script de migration qui effectue ce processus en trois étapes.
C'est (ce que je considérerais) un problème avec SQLite. Cette erreur se produit qu'il y ait des enregistrements dans la table ou non.
Lorsque vous ajoutez une table à partir de zéro, vous pouvez spécifier NOT NULL, ce que vous faites avec la notation ": null => false". Cependant, vous ne pouvez pas le faire lors de l'ajout d'une colonne. La spécification de SQLite indique que vous devez avoir une valeur par défaut pour cela, ce qui est un mauvais choix. L'ajout d'une valeur par défaut n'est pas une option car elle va à l'encontre du but d'avoir une clé étrangère NOT NULL - à savoir, l'intégrité des données.
Voici un moyen de contourner ce problème, et vous pouvez tout faire dans la même migration. REMARQUE: c'est pour le cas où vous n'avez pas déjà d'enregistrements dans la base de données.
class AddDivisionIdToProfile < ActiveRecord::Migration
def self.up
add_column :profiles, :division_id, :integer
change_column :profiles, :division_id, :integer, :null => false
end
def self.down
remove_column :profiles, :division_id
end
end
Nous ajoutons la colonne sans la contrainte NOT NULL, puis modifions immédiatement la colonne pour ajouter la contrainte. Nous pouvons le faire car, bien que SQLite soit apparemment très préoccupé lors d'un ajout de colonne, il n'est pas si difficile avec les changements de colonne. Ceci est une odeur de conception claire dans mon livre.
C'est certainement un hack, mais c'est plus court que plusieurs migrations et cela fonctionnera toujours avec des bases de données SQL plus robustes dans votre environnement de production.
Si vous avez une table avec des lignes existantes, vous devrez mettre à jour les lignes existantes avant d'ajouter votre contrainte null
. Le Guide sur les migrations recommande d'utiliser un modèle local, comme ceci:
Rails 4 et plus:
class AddDivisionIdToProfile < ActiveRecord::Migration
class Profile < ActiveRecord::Base
end
def change
add_column :profiles, :division_id, :integer
Profile.reset_column_information
reversible do |dir|
dir.up { Profile.update_all division_id: Division.first.id }
end
change_column :profiles, :division_id, :integer, :null => false
end
end
Rails 3
class AddDivisionIdToProfile < ActiveRecord::Migration
class Profile < ActiveRecord::Base
end
def change
add_column :profiles, :division_id, :integer
Profile.reset_column_information
Profile.all.each do |profile|
profile.update_attributes!(:division_id => Division.first.id)
end
change_column :profiles, :division_id, :integer, :null => false
end
end