Migracje w Ruby on Rails
Ostatnio część czasu w pracy poświęcam na rozwijaniu projektu RubyTime. Jest to open-source’owa aplikacja napisana w Ruby on Rails do zliczania czasu pracy w małych firmach (ang. time tracker albo time-sheets) i następnego rozliczania pracowników i wystawiania faktur na tej podstawie klientom. Przy dodawaniu nowej funkcjonalności pierwszy raz skorzystałem z mechanizmu migracji, które oferuje Ruby on Rails. Muszę przyznać, że jestem pod wrażeniem prostoty tego rozwiązania.
Przede wszystkim zaskoczyło mnie inne podejście do projektowania baz danych. Oczywiście zastosowane podejście jest pochodną częstego stosowania w rozwijaniu aplikacji RoR metodyk lekkich/zwinnych (XP, Scrum). W nich z założenia przyjmuje się omylność przy projektowaniu, a krótkie iteracje służą do poprawiania/refaktoryzacji aplikacji. Dobre projektowanie baz danych nigdy nie było łatwym zajęciem, a pomyłki były częste. Nie raz zdarzało mi się, że zaprojektowana BD musiała zostać trochę zmieniona ze względu na zbędą redundancję albo efektywność albo czegoś zabrakło.
Ruby on Rails poprzez dostarczenie mechanizmu migracji pozwala na szybkie rozwijanie aplikacji i następnie poprawianie usterek. Trzeba podkreślić, że dodawanie nowej funkcjonalności staje się równie miłe i przyjemne.
Zobaczmy to na przykładzie :).
Powiedzmy, że mamy już istniejącą aplikację. Naglę się zorientowaliśmy, że zapomnieliśmy wprowadzić systemu logowania dla użytkowników :). Czyli mamy model User, a w bazie danych ponad 1000 rekordów. Coś trzeba zrobić. Zacznijmy od wygenerowania nowej migracji:
$ ruby script/generate migration AddLoginAndPassword
exists db/migrate
create db/migrate/002_add_login_and_password.rb
Teraz zabierzmy sie do edytowania pliku 002_add_login_and_password.rb.
#
# Migracja zmienia model użytkownika i dodaje do niego 2 kolumny:
# - login (domyślna wartość to pierwsze słowo nazwy użytkownika)
# - password (domyślna wartość to login użytkownika)
#
class AddLoginAndPassword < ActiveRecord::Migration
def self.up
add_column :users, :login, :string, :null => false
add_column :users, :password, :string, :null => false
User.find(:all).each { |user|
user.login = user.name.scan(/\w+/)[0].strip.downcase
user.password = Digest::SHA1.hexdigest(user.user)
user.save
}
end
def self.down
remove_column :users, :login
remove_column :users, :password
end
end
Powyższy kod jest bardzo czytelny i widać od razu, co on robi. Wartym podkreślenia jest fakt, że migracje pozwalają nie tylko zmieniać strukturę bazy danych, ale także wypełniać ją jakimiś danymi. Jest to bardzo przydatne, gdy wprowadzamy nową funkcjonalność. Teraz zostaje nam tylko dodać do modelu więzy spójności i odpalić:
rake db:migrate VERSION=2
Voilà! ![]()
Nazywam się Wiktor Gworek i jestem gospodarzem tego bloga.
11:02 06-01-2007
Migracje są świetne, ale dlatego, że praktycznie wszystko w Railsach jest przemyślane. Sam w jednym z projektów w php przeportowałem ten mechanizm i zmiany w bazie były wykonywane za pomocą migracji :).
12:06 08-07-2007
Migracje są OK.Polecam wszystkim.
Pozdrawiam autora.
19:26 10-11-2007
Istnieje przy takim migrownaniu pewne niebezpieczeństwo. Zauważmy, że używasz tu modelu User i jego metody find. W niektórych przypadkach może zaistnieć pokusa by użyć innych metod modelu, ale w zdefinowanych przez programistę. Wyobraźmy sobie co się stanie z taką migracją po zmianie nazwy takiej metody...
Migracje przestaną działać, ponieważ model na którym była oparta stara migracja już się zmienił.
Dlatego stosuje się takie rozwiązanie:
Pozwala to na używanie w migracji tylko metod zdefiniowanych przez ActiveRecord i zabezpiecza przed przyszłymi błędami.