2009-05-11 6 views
32

Sto provando a convertire una colonna nella mia app Rails, per ragioni argomentative facciamo finta di provare a cambiare la colonna nella mia tabella users in una rappresentazione di stringa anziché in un int.È possibile utilizzare le Migrazioni su rotaie per convertire i dati?

Nella mia migrazione ho questo;

def.self up 
    add_column :users, :age_text, :string 

    users = User.find(:all) 

    users.each do |u| 
     u.age_text = convert_to_text(u.age) 
     u.save 
    end 
end 

def self.convert_to_text(number) 
    #code here to convert 1 to 'one' etc 
end 

Ma non sembra funzionare, è quello che sto tentando qui anche con le migrazioni possibili?

risposta

56

Quello che stai cercando di fare è possibile, e direi la cosa giusta da fare.

È necessario, tuttavia, ricaricare le informazioni sulla colonna per le classi del modello che si stanno aggiornando nella migrazione, in modo che Rails conosca le nuove colonne. Prova questo:

def.self up 
    add_column :users, :age_text, :string 

    User.reset_column_information 

    users = User.find(:all) 

    users.each do |u| 
     u.age_text = convert_to_text(u.age) 
     u.save 
    end 
end 

In una nota separata, si prega di notare che se il vostro tavolo è di grandi dimensioni, facendo aggiornamenti uno per uno richiederà tempo luuungo .. Fate attenzione con quello.

+2

Quale sarebbe un modo migliore? – Kirschstein

+0

Dipende da cosa stai cercando di fare .. Se è necessario un semplice aggiornamento, puoi semplicemente eseguire un comando SQL con il metodo execute (esegui 'update user set colA = 1'). Ancora una volta, stai attento; se nella tabella Utenti sono presenti 10k utenti, è probabile che il ciclo di looping di ciascuno di essi con la migrazione precedente richieda molto tempo. –

+1

A volte è necessario anche questo User.connection.schema_cache.clear! – hrdwdmrbl

38

Da quando sono nuovo qui non posso commentare quanto sopra, quindi aggiungerò la mia risposta.

Generalmente la manipolazione dei dati nelle migrazioni è un'idea BAD. Le migrazioni con accesso diretto al modello possono rimanere bloccate se la logica del modello cambia.

Immagina nella tua seconda migrazione che hai aggiunto una nuova colonna. Vuoi seminare quella colonna con nuovi dati.

Diciamo anche che qualche settimana dopo si aggiunge una nuova convalida al modello, una convalida che opera su un campo che non esiste ancora nella seconda migrazione. se dovessi costruire il database dalla migrazione 0, avresti qualche problema.

Suggerisco caldamente di utilizzare le migrazioni per modificare colonne e altri mezzi per gestire i dati del database, in particolare quando si passa alla produzione.

+0

Hmm, ottimo punto. Con questo in mente, come gestite la conversione dei dati? – Kirschstein

+1

Compito un rake. È più facile, e anche più facile da testare, anche se lo butto via dopo averlo usato. –

+3

Esiste un modo per garantire che le attività di rake vengano eseguite nella sequenza corretta, insieme alle migrazioni, quindi? Cosa succede se una migrazione eseguita dopo questa viene eliminata dalla colonna obsoleta prima dell'esecuzione dell'attività rake? – Kirschstein

0

Direi che se è possibile "annullare" i dati importati durante il rollback della versione di migrazione, è opportuno inserire le importazioni nella migrazione.

Ad esempio, ho una migrazione che imposta molte tabelle di ricerca e altri metadati. I dati per queste tabelle vengono popolati durante questa fase. Man mano che i dati di queste tabelle di ricerca cambiano, creo nuovi file YAML che memorizzano i metadati e caricano tali file nelle successive migrazioni (e annullano l'operazione YAMLS, ricaricando il file YAML precedente quando si esegue il backup di una versione di migrazione). Questo è abbastanza pulito. Ho i file (in diverse cartelle ben definite nel mio caso), con questi file:

002_setup_meta_data.rb 
002_meta_data.yaml 


007_change_meta_data.rb 
007_meta_data.yaml 

Se stai importando i dati di "produzione" da un altro sistema in transazionali tabelle (non statici), allora direi l'utilizzo delle migrazioni non è appropriato. Poi seguirei il consiglio di Brian Hogan sull'uso dei compiti di rake.

4

Ecco un esempio di migrazione che ho eseguito per convertire i dati. Puoi facilmente convertirlo per usare interi invece di stringhe. Effettuare la conversione in SQL è molto più veloce del caricamento di ogni riga in Rails.

class ConvertCommentTextToText < ActiveRecord::Migration 
    def up 
    add_column :comments, :text_tmp, :text 
    # copy text column 
    execute <<-SQL 
     update comments set text_tmp = text 
    SQL 
    remove_column :comments, :text 
    rename_column :comments, :text_tmp, :text 
    end 

    def down 
    add_column :comments, :text_tmp, :string 
    # copy text column 
    execute <<-SQL 
     update comments set text_tmp = text 
    SQL 
    remove_column :comments, :text 
    rename_column :comments, :text_tmp, :text 
    end 

end 

E per provarlo:

rake db:migrate 
rake db:rollback 
rake db:migrate 
Problemi correlati