Comment changer une colonne Nullable en Non Nullable dans une migration Rails?

193

J'ai créé une colonne de date dans une migration précédente et je l'ai définie comme nullable. Maintenant, je veux le changer pour qu'il ne puisse pas être annulé. Comment procéder en supposant qu'il existe des lignes nulles dans cette base de données? Je suis d'accord pour définir ces colonnes sur Time.now si elles sont actuellement nulles.

Kevin Pang
la source

Réponses:

208

Si vous le faites dans une migration, vous pourriez probablement le faire comme ceci:

# Make sure no null value exist
MyModel.where(date_column: nil).update_all(date_column: Time.now)

# Change the column to not allow null
change_column :my_models, :date_column, :datetime, null: false
DanneManne
la source
1
Juste une note, car cela m'a fait casser ma base de données de développement. Utilisez plutôt la syntaxe de hachage explicite, comme ceci: MyModel.update_all({:date_column => Time.now}, {:date_column => nil}). La requête dans votre forme originale vient de faire en sorte que tous mes modèles aient une valeur nulle dans le champ.
dimitarvp
Merci pour la mise à jour. Je sais que ce n'était pas le cas lorsque j'ai écrit cette réponse, mais je ne me souviens plus quelle version de Ruby ou RoR j'utilisais à l'époque.
DanneManne
1
Utilisez-vous la méthode «up» / «down» dans cette migration, ou pouvez-vous la méthode de changement simple dans la migration?
EE33
2
La changeméthode n'est pas si adaptée à ce cas car (1) la update_allméthode sera exécutée à la fois lors de la migration et d'un retour potentiel. Ce n'est peut-être pas la pire des choses, mais parce que (2) la migration n'a aucun moyen de savoir de quoi la colonne a été modifiée lors d'un retour potentiel. Donc, pour ce cas, je m'en tiendrai à upet down.
DanneManne
2
Pour toute personne intéressée, ma réponse montre comment faire cela en une seule étape.
Rick Smith
171

Dans Rails 4, c'est une meilleure solution (DRYer):

change_column_null :my_models, :date_column, false

Pour vous assurer qu'aucun enregistrement n'existe avec des NULLvaleurs dans cette colonne, vous pouvez transmettre un quatrième paramètre, qui est la valeur par défaut à utiliser pour les enregistrements avec des NULLvaleurs:

change_column_null :my_models, :date_column, false, Time.now
mrbrdo
la source
4
Cela provoque des problèmes lorsque la table a déjà des valeurs nulles. Voir ma réponse
Rick Smith
5
Egalement disponible en 3.2. Possède également un 4ème paramètre pour définir la valeur par défaut où la valeur est nulle.
toxaq
1
Plus 1 pour change_column_null. Cependant, le commentaire de Rick Smith ci-dessus souligne un cas très valable.
0112
Mis à jour pour ajouter la requête de mise à jour des valeurs nulles. Le 4ème paramètre (valeur par défaut) n'est utile que lorsque vous souhaitez également avoir une valeur par défaut pour les enregistrements futurs.
mrbrdo
3
En fait, selon la documentation de Rails 4.2, le 4ème paramètre ne définit PAS de valeur par défaut pour les enregistrements futurs: "La méthode accepte un quatrième argument facultatif pour remplacer les + NULL + s existants par une autre valeur. Veuillez noter que le quatrième argument n'est pas défini. par défaut d'une colonne. "
Mike Fischer
70

Rails 4 (les autres réponses Rails 4 ont des problèmes):

def change
  change_column_null(:users, :admin, false, <put a default value here> )
  # change_column(:users, :admin, :string, :default => "")
end

La modification d'une colonne contenant des valeurs NULL pour ne pas autoriser NULL posera des problèmes. C'est exactement le type de code qui fonctionnera correctement dans votre configuration de développement, puis se bloquera lorsque vous tenterez de le déployer sur votre production LIVE . Vous devez d'abord changer les valeurs NULL en quelque chose de valide, puis interdire les NULL. La 4ème valeur dans change_column_nullfait exactement cela. Consultez la documentation pour plus de détails.

De plus, je préfère généralement définir une valeur par défaut pour le champ afin de ne pas avoir besoin de spécifier la valeur du champ chaque fois que je crée un nouvel objet. J'ai inclus le code commenté pour le faire également.

Rick Smith
la source
3
Pour Rails 4, cela semble être la réponse la plus précise et la plus complète, y compris le paramètre par défaut commenté.
Mike Fischer
4
Si vous ajoutez une nouvelle colonne à une table et que vous souhaitez insérer de nouvelles valeurs pour null, mais que vous ne souhaitez pas ajouter de valeur par défaut pour la colonne, vous pouvez le faire dans votre migration: add_column :users, :admin, :stringthenchange_column_null(:admin, :string, false, "new_value_for_existing_records")
colsen
35

Créez une migration qui a une change_columninstruction avec une :default =>valeur.

change_column :my_table, :my_column, :integer, :default => 0, :null => false

Voir: change_column

En fonction du moteur de base de données, vous devrez peut-être utiliser change_column_null

jessecurry
la source
1
Cela a fonctionné pour moi. Utilisation de MySql localement. Lorsqu'il est poussé et exécuté l'application dans Heroku (Postgres), il a craqué sur une colonne qui n'était pas nulle lorsque je l'écrivais un null - à juste titre. Seul "change_column_null" fonctionnerait ne pouvait pas utiliser "change_column ...: null => false" sur MySql. Merci.
rtfminc
1
alors quelle a été votre migration après change_column_null
js111
1
Postges est plus strict que MySQL - je m'attendrais à ce que cela nécessite change_column_null.
jessecurry
3
@rtfminc Je vous recommande fortement d'utiliser le même moteur de base de données en développement et en production, car cela évite beaucoup de problèmes lorsqu'il s'agit de cas extrêmes.
yagooar
12

Rails 4:

def change
  change_column_null(:users, :admin, false )
end
pirate
la source
1
Veuillez décrire vos réponses.
Wahyu Kristianto
3

Dans Rails 4.02+, selon la documentation, il n'y a pas de méthode comme update_allavec 2 arguments. Au lieu de cela, on peut utiliser ce code:

# Make sure no null value exist
MyModel.where(date_column: nil).update_all(date_column: Time.now)

# Change the column to not allow null
change_column :my_models, :date_column, :datetime, null: false
jmarceli
la source
2

Vous ne pouvez pas utiliser add_timestamps et null: false si vous avez des enregistrements existants, voici donc la solution:

def change
  add_timestamps(:buttons, null: true)

  Button.find_each { |b| b.update(created_at: Time.zone.now, updated_at: Time.zone.now) }

  change_column_null(:buttons, :created_at, false)
  change_column_null(:buttons, :updated_at, false)
end
Nicolas Maloeuvre
la source
0

Selon le joyau Strong Migrations , l'utilisation change_column_nullen production est une mauvaise idée car elle bloque les lectures et les écritures pendant que tous les enregistrements sont vérifiés.

La méthode recommandée pour gérer ces migrations (spécifiques à Postgres) consiste à séparer ce processus en deux migrations.

Un pour modifier la table avec la contrainte:

class SetSomeColumnNotNull < ActiveRecord::Migration[6.0]
  def change
    safety_assured do
      execute 'ALTER TABLE "users" ADD CONSTRAINT "users_some_column_null" CHECK ("some_column" IS NOT NULL) NOT VALID'
    end
  end
end

Et une migration séparée pour le valider:

class ValidateSomeColumnNotNull < ActiveRecord::Migration[6.0]
  def change
    safety_assured do
      execute 'ALTER TABLE "users" VALIDATE CONSTRAINT "users_some_column_null"'
    end
  end
end

Les exemples ci-dessus sont extraits (et légèrement modifiés) de la documentation liée. Apparemment, pour Postgres 12+, vous pouvez également ajouter NOT NULLau schéma, puis supprimer la contrainte après l'exécution de la validation:

class ValidateSomeColumnNotNull < ActiveRecord::Migration[6.0]
  def change
    safety_assured do
      execute 'ALTER TABLE "users" VALIDATE CONSTRAINT "users_some_column_null"'
    end

    # in Postgres 12+, you can then safely set NOT NULL on the column
    change_column_null :users, :some_column, false
    safety_assured do
      execute 'ALTER TABLE "users" DROP CONSTRAINT "users_some_column_null"'
    end
  end
end

Naturellement, cela signifie que votre schéma ne montrera pas que la colonne est NOT NULLpour les versions antérieures de Postgres, donc je vous conseille également de définir une validation au niveau du modèle pour exiger que la valeur soit présente (bien que je suggère la même chose même pour les versions de PG qui permettent cette étape).

En outre, avant d'exécuter ces migrations, vous souhaiterez mettre à jour tous les enregistrements existants avec une valeur autre que null et vous assurer que tout code de production qui écrit dans la table n'écrit pas nullpour la ou les valeurs.

MattD
la source