2010-05-22 7 views
6

Un paio di volte sono stato nella situazione in cui volevo refactoring il design di alcuni modelli e ho finito per mettere la logica di aggiornamento nelle migrazioni. Tuttavia, per quanto ho capito, questa non è una buona pratica (soprattutto perché sei incoraggiato a usare il tuo file di schema per la distribuzione, e non le tue migrazioni). Come gestisci questo tipo di problemi?Inserimento della logica di aggiornamento nelle migrazioni

Per chiarire cosa intendo, dire che ho un modello Utente. Poiché pensavo che ci sarebbero stati solo due tipi di utenti, vale a dire un utente "normale" e un amministratore, ho scelto di utilizzare un semplice campo booleano che diceva se l'utente era un amministratore o meno.

Tuttavia, dopo che ho pensato di aver bisogno di un terzo tipo di utente, forse un moderatore o qualcosa di simile. In questo caso aggiungo un modello UserType (e la migrazione corrispondente) e una seconda migrazione per rimuovere il flag "admin" dalla tabella utente. E qui arriva il problema. Nella migrazione "add_user_type_to_users" devo mappare il valore del flag admin su un tipo di utente. Inoltre, per fare ciò, i tipi di utenti devono esistere, ovvero non posso usare il file di semi, ma piuttosto creare i tipi di utente nella migrazione (anch'essi considerati cattive pratiche). Ecco un codice fittizio che rappresenta la situazione:

class CreateUserTypes < ActiveRecord::Migration 
    def self.up 
     create_table :user_types do |t| 
      t.string :name, :nil => false, :unique => true 
     end 

     #Create basic types (can not put in seed, because of future migration dependency) 
     UserType.create!(:name => "BASIC") 
     UserType.create!(:name => "MODERATOR") 
     UserType.create!(:name => "ADMINISTRATOR") 
    end 

    def self.down 
     drop_table :user_types 
    end 
end 

class AddTypeIdToUsers < ActiveRecord::Migration 
    def self.up 
     add_column :users, :type_id, :integer 

     #Determine type via the admin flag 
     basic = UserType.find_by_name("BASIC") 
     admin = UserType.find_by_name("ADMINISTRATOR") 
     User.all.each {|u| u.update_attribute(:type_id, (u.admin?) ? admin.id : basic.id)} 

     #Remove the admin flag 
     remove_column :users, :admin 

     #Add foreign key 
     execute "alter table users add constraint fk_user_type_id 
      foreign key (type_id) references user_types (id)" 
    end 

    def self.down 
     #Re-add the admin flag 
     add_column :users, :admin, :boolean, :default => false 

     #Reset the admin flag (this is the problematic update code) 
     admin = UserType.find_by_name("ADMINISTRATOR") 

     execute "update users set admin=true where type_id=#{admin.id}" 

     #Remove foreign key constraint 
     execute "alter table users drop foreign key fk_user_type_id" 

     #Drop the type_id column 
     remove_column :users, :type_id 
    end 
end 

Come potete vedere ci sono due parti problematiche. Prima la parte di creazione riga nel primo modello, che è necessaria se si desidera eseguire tutte le migrazioni in una riga, quindi la parte "aggiornamento" nella seconda migrazione che associa la colonna "admin" alla colonna "type_id".

Qualche consiglio?

risposta

1

Trovo più "non convenzionale" che si usi fk di quello che si carica UserType con il vecchio User.admin, che suppongo capiti abbastanza spesso.

Se si utilizzano fk's si ottengono brutti errori mysql che confondono l'utente. Se altrimenti si utilizzano le convalide e gli hook di AR per applicare l'integrità referenziale, si ottengono messaggi di errore abbastanza ben integrati che non interrompono il flusso dell'esperienza utente della propria app.

Non preoccuparti di una migrazione che verrà eseguita una sola volta e pensa alla logica aziendale che stai inserendo all'esterno del codice.

Questo è tutto questione di opinione/convenzione, ma spero che tu possa trovare utili le mie intuizioni.

+0

Quando si tratta di chiavi esterne, trovo che siano molto utili, specialmente quando si tratta di dati di importanza strategica. Mi aiuta a evitare i dati penzolanti ea rilevare se mi sono perso l'applicazione di ganci e simili dal lato di Rails. Anche se hai ragione, potrebbero darti dei brutti messaggi di errore. –

+1

Vedo, è una questione di scelta. se l'integrità dei dati oltre i backup è superiore a UX, fk è la strada da percorrere. In tal caso si dispone di un plug-in per la gestione di fk dalle migrazioni: http://agilewebdevelopment.com/plugins/foreign_key_migrations – Oinak

+0

Ok. Grazie per il suggerimento! –

0

Il file db/seeds.rb è comunemente usato per questo scopo - record messi in là verrà caricato come parte di rake db:setup

Tuttavia, ho sempre trovato rotaie cade su questo problema. Ho pensato di scrivere un plug-in che fornisca una cartella db/seed, abbia i seedfile datestamped per aggiungere record (.yml, forse) e tiene traccia dei dati seed in una tabella di sistema in modo che possa essere ripristinata/aggiornata.

+1

Come potete vedere nel codice, non posso usare le sementi come vorrei applicare diverse migrazioni contemporaneamente e l'ultima migrazione dipende dal fatto che ci sono dati nel database. Sarebbe bello se si potesse avere un file seme per migrazione e che i file seme verrebbero applicati tra una migrazione e l'altra. Quindi prima 20100524 ... viene eseguito do_something.rb e quindi 20100524 ... seed_something.rb esegue il seeding richiesto per questa migrazione. –