Začínáme s Migracemi
Skoro každé rozšiřování nebo upravování Rails aplikace nějak souvisí s databází. Většinou pouze přidáváme nějakou tu logiku do modelů a controllerů a databázi pouze používáme. Čas od času se ale databázové schéma mění. Na změny databázového schématu existuje spousta grafických a klikacích nástrojů, někdo používá konzoli databáze a píše SQL DDL příkazy ručně. Já mám rád Rails a ještě více Ruby. Migrace jsou nástroj jak změny v databázovém schématu definovat v Ruby. V tomto článku bych chtěl popsat jak migrace fungují, jak se používají a poradit jak migrace nasadit na již rozběhnutém projektu, kde se na ně zapomnělo.
Migrační teorie
Migrace jsou sobory popisující změnu v databázovém schématu. Technicky vzato jsou migrace třídy žijící v adresáři db/migrate vaší Rails aplikace. Všechny migrace jsou potomky třídy ActiveRecord::Migration, jejíž metody bohatě využívají. Každá migrace musí obsahovat classmetody up a down, proč to pochopíme až si vysvětlíme jak migrace fungují.
Migrace je nejlepší spouštět pomocí rake tasku db:migrate. Pokud chcete migrace spouštět ze svého kódu doporučuji přečíst si kód tohoto tasku, který najdete v railties/lib/tasks/databases.rake. Třída provádějící migrace je ActiveRecord::Migrator (bez dokumentace) a funguje tak, že porovná verzi databáze uloženou v tabulce schema_info a číslo poslední migrace v adresáři db/migrate a pokud najde nějaké migrace s číslem větším než je číslo databáze, tak tyto migrace jednu podruhé spouští.
Pro správnou funkčnost migrací jsou potřeba dvě věci:
- aby v databázi existovala tabulka schéma_info s jedním int sloupečkem version
- aby třídy migrací byly v souborech pojmenovaných xxx_nazev_migrace.rb, kde xxx je číslo migrace
Toto nás, ale nemusí trápit, protože pokud budeme používat Rails generátory a rake tasky tak se vše udělá jako obvykle samo.
Migrace v praxi
Nejzákladnějším použitím migrací je vytvoření nového modelu, pro který potřebujeme vytvořit tabulku. Model vytvoříme pomocí Rails generátoru script/generace model NazevModelu. Toto nám vytvoří modelovou třídu, unit test a migraci, která se bude jmenovat xxx_create_nazev_modelus.rb. Dejme tomu, že vytváříme model s názvem Item (jak originální) pomocí script/generace model Item a vytvoří se nám migrace 001_create_items.rb s poněkud nudným kódem pro vytvoření prázdné tabulky.
class CreateItems < ActiveRecord::Migration
def self.up
create_table :items do |t|
end
end def self.down
drop_table :items
end
end
Metoda up se volá, když se migruje nahoru z verze nižší na verzi vyšší. Metoda down se volá když se migruje dolů z verze vyšší na verzi nižší. Migrování dolů se dá docílit tak, že tasku db:migrate předáte parametr VERSION=x. Databáze se pak zmigruje zpět na verzi x. Všechny metody pro úpravy databáze najdete v dokumentaci třídy ActiveRecord::Migration. I když je předchozí migrace skoro prázdná, vytvořená tabulka by obsahovala sloupec id pro primární klíč. K čemu by nám byl model bez id, že jo? Kód pro vytvoření tabulky už nám Rails vygenerovaly přidáme tedy pár sloupečků a nějaké indexy.
class CreateItems < ActiveRecord::Migration
def self.up
create_table :items do |t|
t.column :name, :string
t.column :numeric, :integer, :null => false, :default => 1
t.column :description, :text
t.column :parent_id, :integer
end
add_index :items, :parent_id
end def self.down
drop_table :items
end
end
Teď už stačí pouze spustit rake db:migrate a tabulka se na nás směje v databázi.
Ejhle, změna
Další užitečný Rails generátor je generátor migrací. Přijde vhod, když prostě potřebujeme něco změnit. Zavoláním script/generace migration add_position_to_items se nám vygeneruje prázdná migrace, na které si předvedeme další možnosti migrací.
class AddPositionToItems < ActiveRecord::Migration
def self.up
# přidání sloupečku
add_column :items, :position, :integer
# přejmenování sloupečku
rename_column :items, :description, :desc
# přidání sloupečku
add_column :items, :short_desc, :integer
#
# načteme z DB nové atributy objektu
Item.reset_column_information
#
# aktualizace záznamů
Item.find(:all).each do |item|
# inicializace nového sloupečku
item.short_desc = item.desc[0, 20]
item.save
end
end def self.down
# vrátíme provedené změny
remove_column :items, :position
rename_column :items, :desc, :description
remove_column :items, :short_desc
end
end
O nasazení migrací na rozběhnuté projekty a dalších fíčurách snad příšte.