web-dev-qa-db-fra.com

Rails migrations - change_column avec conversion de type

Je suis déjà allé sur Google un peu et il semble qu'il n'y ait pas de réponse satisfaisante à mon problème.

J'ai une table avec une colonne de type chaîne. Je voudrais exécuter la migration suivante:

class ChangeColumnToBoolean < ActiveRecord::Migration
    def up
        change_column :users, :smoking, :boolean
    end
end

Lorsque je lance ceci, j'obtiens l'erreur suivante

PG::Error: ERROR:  column "smoking" cannot be cast automatically to type boolean
HINT:  Specify a USING expression to perform the conversion.
: ALTER TABLE "users" ALTER COLUMN "smoking" TYPE boolean

Je sais que je peux effectuer cette migration en utilisant du SQL pur, mais ce serait quand même mieux si je pouvais le faire avec Rails. J'ai parcouru le code Rails et ne semble pas y avoir une telle possibilité, mais peut-être que quelqu'un connaît un moyen?

Je ne suis pas intéressé par: - SQL pur - supprimer la colonne - créer une autre colonne, convertir des données, supprimer l'original puis renommer

52
Mike Szyndel

Depuis que j'utilise Postgres, je suis allé avec la solution SQL pour l'instant. Requête utilisée:

    execute 'ALTER TABLE "users" ALTER COLUMN "smoking" TYPE boolean USING CASE WHEN "flatshare"=\'true\' THEN \'t\'::boolean ELSE \'f\'::boolean END'

Cela ne fonctionne que si l'on a un champ rempli de chaînes vraies/fausses (comme l'aide à la collecte de boutons radio par défaut avec le type booléen forcé)

5
Mike Szyndel

Si vos chaînes dans la colonne smoking sont déjà des valeurs booléennes valides, l'instruction suivante changera le type de colonne sans perdre de données:

change_column :users, :smoking, 'boolean USING CAST(smoking AS boolean)'

De même, vous pouvez utiliser cette instruction pour convertir des colonnes en entier:

change_column :table_name, :column_name, 'integer USING CAST(column_name AS integer)'

J'utilise Postgres. Je ne sais pas si cette solution fonctionne pour d'autres bases de données.

108
Brian

Toutes les bases de données ne permettent pas de changer le type de colonne, l'approche généralement adoptée consiste à ajouter une nouvelle colonne du type souhaité, à apporter des données, à supprimer l'ancienne colonne et à renommer la nouvelle.

add_column :users, :smoking_tmp, :boolean

User.reset_column_information # make the new column available to model methods
User.all.each do |user|
  user.smoking_tmp = user.smoking == 1 ? true : false # If smoking was an int, for example
  user.save
end

# OR as an update all call, set a default of false on the new column then update all to true if appropriate.
User.where(:smoking => 1).update_all(:smoking_tmp = true) 

remove_column :users, :smoking
rename_column :users, :smoking_tmp, :smoking
34
Matt

Donc bon pour booléen en postgres:

change_column :table_name, :field,'boolean USING (CASE field WHEN \'your any string as true\' THEN \'t\'::boolean ELSE \'f\'::boolean END)'

et vous pouvez ajouter des conditions WHEN - THEN supplémentaires dans votre expression

Pour les autres serveurs de base de données, l'expression sera construite sur la base de la syntaxe de votre serveur de base de données, mais le principe est le même. Seul algorithme de conversion manuelle, entièrement sans SQL il n'y en a malheureusement pas assez.

Way avec la syntaxe change_column :table, :filed, 'boolean USING CAST(field AS boolean)' ne convient que si le contenu du champ quelque chose comme: true/false/null