Modele i migracje

W poprzednim artykule opisałem strukturę tworzenia nowego projektu w Phoeniksie. Dzisiaj przeanalizujemy jak migracje i modele Phoeniksa mają się do ActiveRecord oraz ActiveRecord::Migration w Railsach.

Skoro w tamtym artykule zajęliśmy się stworzeniem prostej aplikacji do publikowania blogpostów, napiszmy dzisiaj model postowania. Tak jak poprzednio, również i teraz możemy wykorzystać gotowe generatory do utworzenia struktury naszej aplikacji. W celu napisania nowego posta, użyjemy następującej komendy:

Powyższa komenda jest podobna do jej Railsowego odpowiednika:

Jeśli jeszcze nie zdążyliście zauważyć, napomknę krótko, że twórcy Phoeniksa mocno inspirowali się Railsami. Wykonanie wspomnianego kodu, zupełnie jak w Railsach, tworzy model, widoki CRUD, kontroler oraz migrację. Różnica pomiędzy Phoeniksem a Railsami zawiera się w tym, że Phoenix nie dodaje automatycznie ścieżek do naszego routera (zostaniecie jednak o tym powiadomieni, jeśli użyjecie tego generatora), zatem dodamy te ścieżki poprzez dopisanie makra zasobów do naszego routera:

Podobnie jak w Railsach, w Phoeniksie również należy zmigrować zmiany do bazy danych, by stworzyć nową tabelę. Spójrzmy jednak wpierw na plik migracji wygenerowany przez generator:

Migracje w Phoeniksie bardzo przypominają te z Railsów. Największa rozbieżność pomiędzy nimi to odmienne sposoby zapisu, które wynikają z różnic w paradygmatach: Ruby używa notacji kropkowej, zaś Elixir rachunku lambda. No dobrze, ale co jeśli chcemy zaimplementować bardziej skomplikowaną migrację, ale trudno wykonać rollback z modułu migracji? Niestety, mam dla was kolejną nudną odpowiedź: tak samo jak w Railsach, używamy osobnych metod "up" i "down". Osobiście bardzo mi się to podoba, bo uważam, że migracje w Railsach są świetne i nigdy nie miałem z nimi problemu, jeśli tylko były używane we właściwy sposób. 

Wróćmy teraz do naszego modelu postowania. Modele w Phoeniksie, to tak naprawdę eliksirowe strukty, które to z kolei są w istocie starymi dobrymi mapami. Pozwalają one na wygodniejsze adresowanie pól w struktach poprzez użycie notacji kropkowej, a przez to nie musimy używać funkcji Map.get/3. Poniżej zamieszczam nasz model postowania:

 Pierwsza linia modelu zawiera kod importujący funkcje odpowiednie dla modeli w naszej aplikacji. Domyślnie importowane są funkcje dla Ecto, która jest czymś w rodzaju biblioteki ORM (w gruncie rzeczy to wrapper bazy danych) dla Elixira. Użyjemy tych funkcji dopiero później, więc przejdźmy na razie dalej. W następnych kilku liniach mamy kod definiujący wzór tabeli używanej przez model. Definicja zawiera nazwę tabeli bazy danych, pola, typy pól, wartości domyślne oraz makro znacznika czasu, które to makro – znów bardzo podobnie do Railsów – dodaje 3 domyślne pola: id, created_at i updated_at.

W tym miejscu umieszczamy również wszelkie informacje o relacjach zawartych w modelu. Bardzo podoba mi się to, że wzór tabeli jest zawarty w modelu, przez co nie jestem zmuszony do używania osobnych bibliotek, takich jak np. annotate w modelach ActiveRecord.

Następna metoda to changeset i tutaj już możemy zobaczyć bardzo fajny wzorczec w akcji. Changeset to funkcja, która bierze już istniejący model (pierwszy parametr – struct) oraz dodaje do niego nowe parametry (np. z formularza HTML) i sprawdza czy ten model działa poprawnie po nowych zmianach. "A co w tym takiego niesamowitego?", spytacie zapewne. Przecież w Railsach mamy to już od paru lat. Cała różnica zawiera się w zakresie sprawdzania poprawności. "Tradycyjny" railsowy sposób na sprawdzanie poprawności to dodać je do globalnego zakresu modelu. W Phoeniksie zaś robimy to w konkretnej funkcji. Jeżeli logika biznesowa wymusza zastosowanie innego sposobu sprawdzania poprawności, np. w zależności od typu użytkownika, możemy po prostu napisać kolejną funkcję changeset. Oczywiście, w świecie Ruby również mamy dostępne tego typu rozwiązania – wystarczy użyć dry-validation albo form objectów. W istocie jednak niewiele projektów w ekosystemie Railsów wykorzystuje te sposoby, a w Phoeniksie są one rozwiązaniem domyślnym. Możemy w funkcji changeset zobaczyć też jak działa operator pipe – jeżeli tylko mieliście do czynienia z bardziej skomplikowanymi komendami basha, nie będzie dla was to niczym nowym. Operator pipe najpierw bierze nasz strukt, którym są nasze dane, nadaje mu nowe parametry (samego obiektu nie zmieniamy – niezmienność obiektów!), a na końcu weryfikuje poprawność parametrów w nowym strukcie. Istnieją oczywiście także inne funkcje sprawdzające, których możecie użyć – wszystkie znajdziecie w dokumentacji Ecto.Changeset.

To by było na tyle jeśli chodzi o dzisiejszy wpis. Dowiedzieliśmy się dzisiaj jakie są różnice pomiędzy migracjami i modelami w Railsach i w Phoeniksie. W telegraficznym skrócie: migracje w obu językach są bardzo podobne, zaś modele różnią się w dosyć znacznym stopniu, gdyż Phoenix forsuje inne podejście niż Railsowe ActiveRecord. Domyślny styl w Phoeniksie to "A może tak nie tworzyć globalnych zasad sprawdzania w modelu, a potem ograniczać ich zakresów?". Muszę przyznać, że bardzo mi się ten styl podoba. Podczas mojej przygody z Railsami, zrozumiałem, że lżejszy model to lepszy model. Im mniej stanów, o których muszę pomyśleć, tym lepiej.

W następnym odcinku serii popracujemy nad cyklem request-response oraz nad tym jak wpływać na niego poprzez pipeline'y. Oto link do niego https://bulldogjob.pl/articles/411-phoenix-cykl-zapytania-i-odpowiedzi-request-response.

Bartosz Łęcki, Ruby on Rails Developer, Netguru