Ponad 4 miesiące temu światem Ruby’iego zatrzęsła wiadomość o połączeniu się 2 popularnych frameworków – Ruby on Rails i Merba. Dzisiaj, tylko kilka dni dzieli nas od ujrzenia pierwszych aplikacji, które będą napisane w Rails 3. Carl Lerche (twórca między innymi routera do merba) oraz Yehuda Katz na tegorocznym RailsConf będą mówić o Rails3 i mountable apps.
Na stabilną wersję będzie trzeba jeszcze trochę poczekać, ale trzymam kciuki, żeby na RailsConf rzeczywiście udało im się pokazać działającą aplikację.
W tym poście chciałbym krótko opisać to co ma się znaleźć w Rails 3. Z niemałą niecierpliwością śledzę newsy i doniesienia o nowych railsach i nie mogę się już doczekać niektórych usprawnień.
Zwolennicy Merba bardzo podkreślają w swoim frameworku niezależność od konkretnych technologii. Było to reklamowane głównie jako “ORM agnosticism” czyli możliwość podpięcia do Merba jednego z wielu “ORMów” (DataMapper, ActiveRecord, Sequel) w porównaniu do Railsowej monolityczności w postaci jednego słusznego ActiveRecorda.
Jaki właściwie jest problem w używaniu innego ORMa w naszej aplikacji? Na poziomie modeli i kontrolerów nie ma to z reguły większego znaczenia. Przecież Railsów nie obchodzi czy klasa w katalogu app/models dziedziczy po ActiveRecord::Base, czy dołącza moduł: include DataMapper::Resource
Kłopot zaczyna się kiedy chce się korzystać z wielu helperów, które jako argument przyjmują obiekt ActiveRecord. Najprostszy przykład:
<% form_for @article do |f| %>
<%= f.text_field :title %>
<% end %>
Jeżeli @article nie jest obiektem AR, to poprawność działania tej metody zależy tylko od tego czy interfejs danego ORMa tutaj używany jest taki sam. W tym akurat przypadku problemem może być sprawdzanie błędów w walidacji. Railsy domyślnie dodają wrapper do pól z błędami, więc jeżeli ORM ma inny interfejs do pobrania informacji o błędach, to metoda po prostu nie zadziała.
W tym momencie jest to w Railsach zrobione z użyciem alias_method_chain i jeżeli obiekt nie ma zaimplementowanej metody errors, to tag pozostaje niezmieniony, więc można samemu dołączyć swoją własną implementację lub dodać potrzebne metody do modelu. Jednak wszyscy chyba się zgodzą, że nie jest to najbardziej elegancki sposób.
Jeżeli chodzi o Merba, to sprawa wygląda tylko trochę lepiej niż w railsach. Jest co prawda wsparcie dla 3 najpopularniejszych ORMów, ale implementacja nie pozwala na łatwe dodanie kolejnych ORMów bez grzebania w kodzie. Jeżeli ORM, którego chcesz używać ma interfejs zgodny z DataMapperem, Sequelem lub ActiveRecordem, to wszystko jest ok, ale jeżeli jest to coś innego, to masz problem.
Odpowiedzią na to ma być ActionORM. Czym jest ActionORM? Jest to proste proxy dla obiektów. ActionORM udostępnia API takie jak ActiveRecord (dzięki temu w kodzie railsów nie będzie trzeba wiele zmieniać). W prostych słowach ActionORM ma sprawić, że obiekty innych ORMów będą wyglądały jak obiekty ActiveRecord (czyli klasyczne wykorzystanie duck typing)
Tutaj jest przykład proxy dla Datamappera
Różnice są jak widać niewielkie, ale podejrzewam, że będzie można dodać więcej metod, które się różnią pomiędzy implementacjami (oczywiście w granicach rozsądku, sprowadzanie całego interfejsu do jednego przypadku było by pozbawione sensu, chodzi o proste operacje, które każdy ORM ma rozwiązane podobnie, ale różni się API).
Railsy od jakiegoś czasu wykorzystują w pełni dobrodziejstwa Rack. Co więcej, różne części frameworka są zaimplementowane jako rack middlewares. Kontroler, router, obsługa sesji i wiele innych rzeczy to teraz po prostu kolejne middlewares (przepraszam za te anglojęzyczne wstawki, ale naprawdę nie mam pojęcia jak to ładnie przetłumaczyć). Dzięki temu i dodaniu Rails metal aplikacje są coraz bardziej modularne.
Programiści railsów dążą w tej chwili do jeszcze szerszego wykorzystania racka. Wszystko to idzie w stronę jak najłatwiejszego połączenia komponentów różnego typu. Na przykład możliwość podłączenia do routera kontrolerów railsowych, aplikacji sintatry i aplikacji merba (jako jedną aplikację oczywiście).
Największą i najlepszą według mnie zmianą w railsach jest refaktoryzacja kodu ActionPack. Na czym ona polega? W bardzo prostych słowach: stworzony jest AbstractController, z którego dziedziczyć będą inne kontrolery, a w samym kodzie jest zastosowana dużo większa enkapsulacja. Jakie to ma znaczenie dla użytkowników railsów? Będzie istniał jeden interfejs dla wszystkich kontrolerów, dzięki czemu na przykład ActionController i ActionMailer będą mogły współdzielić funkcjonalność (żeby nie iść daleko, dopiero niedawno do mailera dodana została obsługa layoutów). Bardzo łatwe będzie też dodanie Parts znanych z merba (PartsController również dziedziczy w Merbie z AbstractControllera).
W chwili obecnej podobną funkcjonalność do Merb Parts udostępniają Rails Cells, ale nie jest to rozwiązanie idealne (implementacja jest zagmatwana, w cellach widoczne są zmienne instancji z kontrolerów i innych celli, podejrzewam, że jest to wolniejsze niż parts).
Co jeszcze? Yehuda Katz wziął się między innymi za optymalizację callbacków, czy bloku respond_to. Polecam również inne artykuły na temat refaktoryzacji na jego blogu.
Na pewno szybszy będzie też router. W chwili obecnej największym problemem jest to, że router generuje bardzo dużo metod (wszystkie metody _url i path), co w rezultacie przyczynia się do bardzo wolnego startu aplikacji. W czasach mongreli nie miało to dużego znaczenia, bo aplikacje były odpalane raz i tylko sporadycznie restartowane. Teraz kiedy na wielu serwerach zainstalowany jest modpassenger, czas startu aplikacji jest dużo ważniejszy – instancje, które nie dostają requestów przez 5 minut (domyślne ustawienie) są wyłączane dzięki czemu nie zajmują zasobów.
Nowy router można przetestować już teraz. Po więcej odsyłam na the merbist
Jest to jedna z tych rzeczy, na które czekam w Railsach 3 najbardziej. Dosłownie liczę godziny do rails conf i prezentacji na ich temat (mam nadzieję, że będą nagrywane, bo niestety nie miałem wolnych kilku tysięcy złotych, żeby się pojawić osobiście na konferencji ;-).
Jakieś namiastki mountable apps są już dostępne w postaci Rails Engines i Merb Slices. Jest to jakieś rozwiązanie, ale na pewno dalekie od idealnego. Żeby skorzystać z którejś z tych opcji trzeba napisać specyficzny rodzaj aplikacji, a każde z tych rozwiązań jest w niektórych miejscach niedopracowane. Celem mountable apps będzie możliwość łatwego połączenia 2 aplikacji bez żadnych zmian w kodzie.
Jedną z rzeczy, którą wypromował Merb jest publiczne i prywatne api. Dzięki temu pisanie pluginów nie wiąże się tam z używaniem alias_method_chain i ogólnie pojętym monkey patchingiem na każdym kroku. A jeżeli czegoś nie da się zrobić używając tylko publicznego api, to jest to bug. Jest to chyba jedna z trudniejszych rzeczy w całej refaktoryzacji. Nie wiadomo dokładnie czego będą potrzebować twórcy pluginów, niektóre metody trzeba będzie dopiero stworzyć.
Ogromnym plusem takiego podejścia jest to, że twórcy frameworka nie muszą się martwić o prywatne metody, ważne żeby publiczne API się nie zmieniało. Dzięki temu pluginy napisane zgodnie z wytycznymi, czyli nie używając prywatnego API nie będą się wysypywały na każdej kolejnej wersji (co bardzo często można zaobserwować w tym momencie), a jeżeli coś się zmieni w publicznym API przy okazji większych zmian, to twórcy pluginów będą mogli się o tym łatwo dowiedzieć bez śledzenia commitów na githubie.
Jestem zdania, że połączenie merba i railsów to jedna z najlepszych rzeczy, która mogła się przytrafić programistom webowym. Railsy będą szybsze, bardziej modularne i łatwiejsze w rozszerzaniu. Tylko czekać na wyniki refaktoryzacji :)
Postaram się w najbliższych tygodniach pisać o tym co dzieje się z Railsami3, jak idą prace i co jeszcze ciekawego będzie można zobaczyć.