Usunięcie modelu z przeniesieniem danych

Mam model:

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ąć?

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.

Czy jest tu jakaś luka? Chyba nie.

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.

[quote=y3ti]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.[/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 :slight_smile:

Właściwie to powinienem założyć, że nie piszesz o tej prostszej części :slight_smile:

[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ę.

Przy okazji, szukając rozwiązania natknąłem się na poniższy wpis - trochę archeologiczny, ale chyba wciąż aktualny :slight_smile:
http://scottstuff.net/blog/2005/10/31/migrating-in-two-dimensions