class Parent < ActiveRecord::Base
has_many :items
end
class Item
attr_accessible :description
end
Doszedłem do wniosku, że nie ma sensu utrzymywać dodatkowego modelu, chcę mieć description jako atrybut w Parent. Zatem:
[code:ruby]# Model
class Parent < ActiveRecord::Base
attr_accessible :description
end
Migracja
class AddDescriptionToParent < ActiveRecord::Migration
def up
add_column :parents, :description, :text
Parent.reset_column_information
Parent.all.each do |parent|
d = parent.items.map(&:description).join("\n")
parent.update_attributes(:description => d)
end
drop_table :items
end
def down
# Odwrotnie …
end
end[/code]
Problem w tym, że usuwając w Parent powiązanie has_many :items - i w ogóle usuwając model Item, bo bez sensu zostawiać nieużywany kod - nie zadziała parent.items w migracji. I klops.
Drugie podejście: mogę przenieść dane (albo przynajmniej pobrać z tabelki items) czystym SQL bez pośrednictwa ActiveRecord. Ale może da się to jakoś ładniej ogarnąć?
Inne rozwiązanie to nie używanie modeli w migracjach i korzystanie z czystego SQL:
Plusy:
uniezależniamy kod od modeli - ja wyznaję zasadę, aby nie korzystać z modeli bezpośrednio w migracjach.
mamy duży wydajniejszy kod. Przy trywialnych migracjach jest to pomijalna korzyść, jednak gdy migracja obejmuje zaledwie kilka tabel po kilkaset tysięcy rekordów to mamy przyśpieszenie o rząd wielkości.
Minusy:
Czasem trzeba się nagimnastykować z SQL-em, więc jeśli ktoś nie jest biegły to lepiej zrobić to przy pomocy modeli railsowych.
Czasem trzeba się nagimnastykować z SQL-em, więc jeśli ktoś nie jest biegły to lepiej zrobić to przy pomocy modeli railsowych.[/quote]
Wiadomo, wszystko ma swoje zady i walety. W tym przypadku czysty SQL chyba jest dobrym rozwiązaniem. W bardziej nietrywialnych przypadkach wydaje mi się, że warto skorzystać z wsparcia AR, np. walidacji. Migracja danych ma to do siebie, że nie da się dla niej napisać testów. Im więcej możemy określić “niezmienników” tym lepiej. W czystym SQLu jest to dużo trudniejsze.
E, no bez przesady. Może niezbyt często, ale z raz czy dwa się zdarzało.
class ChangeUserProfiles < ActiveRecord::Migration
def up
UserProfilesMigrator.new.migrate
end
end
i teraz wystarczy tylko napisać odpowiednie testy dla tej klasy.
E, no bez przesady. Może niezbyt często, ale z raz czy dwa się zdarzało.[/quote]
Nie chodziło mi o to, że jest to niewykonalne “technicznie”. Chodziło mi o to, że jedyny test to jest zmigrowanie danych i zobaczenie wyniku.
Tzn. zwykle kiedy system “żyje” mamy w nim dane, które niekoniecznie spełniają wszystkie aktualne założenia. Powiedzmy najbardziej banalna sprawa - mieliśmy pole “name”, które teraz dzielimy na imię i nazwisko (celowo pomijam tutaj kwestię normalizacji danych - praktycznie zawsze znajdzie się jakieś pole, które w pewnym momencie w przyszłości będziemy chcieli rozbić na mniejsze fragmenty, czy przekształcić w model; analizowany przykład jest akurat w drugą stronę, ale nie ma to znaczenia). No i musimy sobie zrobić jakąś regułę podziału. Ale co jeśli ktoś wpisał dwa imiona, albo nie wpisał nazwiska? etc. itp. Oczywiście teoretycznie normalizacja by pomogła, ale skoro migrujemy dane, to najwyraźniej jej nie zrobiliśmy. Oczywiście możemy wymyśleć sto różnych przypadków szczególnych w testach, ale w danych zawsze będzie ten sto pierwszy przypadek, który wyjdzie dopiero przy migracji.
I w tej sytuacji ograniczenia nałożone na model mogą pomóc wychwycić te przypadki (choć z pewnością nie wszystkie).
To miałem na myśli, pisząc o nietestowalnych migracjach
[quote=apohllo]Scenariusz:
0. Usuń definicję modelu w app/models 1. Dodaj definicję modelu do migracji
2. Zmigruj w migracji dane ze starej tabeli
3. Na końcu usuń tabelę w BD.[/quote]
Ha, punkt 1 chyba załatwia sprawę.