Cells i obiekty widoku: komponenty w Rails
Partiale plus helpery plus zmienne instancji czynią widoki Rails trudnymi do reużycia i niemożliwymi do testowania. Deep-dive o komponentach widoku w Rails z Cells — enkapsulowane, testowalne jednostki UI i dlaczego idea komponentów przychodzi na serwer.
Świat frontu zreorganizował się wokół komponentów — samodzielnych jednostek UI, które
posiadają swój markup, logikę i stan — i była to prawdziwa poprawa. Tymczasem widoki Rails
pozostały w dużej mierze takie, jak były: partiale ciągnące dane ze współdzielonych zmiennych
instancji, metody helperów rozrzucone w modułach i logika wpełzająca w szablony. Ten model
trzeszczy przy skali — widoki stają się trudne do reużycia, niemożliwe do testowania w izolacji i
splątane z kontrolerami ustawiającymi ich @zmienne. Komponenty widoku wnoszą ideę
komponentu na serwer, a w 2018 ustalonym sposobem zrobienia tego w Rails jest gem Cells. Oto
argument za nimi.
Problem z domyślną warstwą widoku
Rozważ „kartę produktu” pokazaną na stronie głównej, stronie kategorii i wynikach wyszukiwania. W czystym Rails to partial i działa — dopóki nie przyjrzysz się bliżej:
- Zależy od otaczającego stanu. Partial sięga po
@productalbo oczekuje przekazanych locals akurat tak; jego zależności są niejawne, ustawiane przez kontroler, który go wyrenderował. Reużyj go gdzieś nowo, a musisz pamiętać, by odtworzyć to ustawienie. - Jego logika nie ma domu. Karta potrzebuje sformatowanej ceny, plakietki, przyciętego opisu — logiki prezentacji, która ląduje w helperach (globalnych, nieumocowanych) albo inline w szablonie (nietestowalna).
- Nie da się jej przetestować w izolacji. By przetestować, jak karta produktu się renderuje, zasadniczo musisz wyrenderować akcję kontrolera i sparsować HTML, bo partial nie jest rzeczą, którą możesz zainstancjować — to fragment istniejący tylko w trakcie żądania.
To te same skargi, które pchnęły front ku komponentom: jednostka UI nie jest prawdziwym, samodzielnym, testowalnym obiektem. To fragment zależny od otoczenia.
Czym jest Cell
Cell to komponent widoku jako prawdziwy obiekt: klasa Ruby enkapsulująca kawałek UI — własny szablon, własną logikę i jawny interfejs dla danych, których potrzebuje. Instancjujesz ją jej wejściami, a ona renderuje samą siebie:
# app/cells/product_cell.rb
class ProductCell < Cell::ViewModel
def show
render # renderuje app/cells/product/show.haml
end
private
def price
number_to_currency(model.price)
end
def badge
model.eco? ? "Eco" : nil
end
end
-# app/cells/product/show.haml
.card
%h3= model.name
%span.price= price
- if badge
%span.badge= badge
A renderujesz ją jawnie, przekazując dokładnie to, czego potrzebuje:
<%= cell(:product, @product) %>
<%# w kolekcji: %>
<%= cell(:product, collection: @products) %>
Zmiana jest fundamentalna: karta jest teraz klasą, z jawnym wejściem (model), jej logiką jako
metodami na tej klasie i jej szablonem należącym do niej. Nie ma otaczającego @product;
wręczasz jej produkt. To samodzielny komponent, nie fragment.
Dlaczego to lepsze
Zyski odzwierciedlają dokładnie to, dlaczego komponenty wygrały na froncie:
-
Enkapsulacja. Wszystko o karcie produktu — markup, formatowanie ceny, logika plakietki — żyje w jednym miejscu,
ProductCelli jego szablonie. By zrozumieć lub zmienić kartę, otwierasz jeden komponent, nie partial plus trzy helpery plus kontroler. -
Jawne zależności. Cell deklaruje, czego potrzebuje. Nie ma ukrytego polegania na ustawionych przez kontroler zmiennych instancji, więc jest naprawdę reużywalna — wyrenderuj ją gdziekolwiek, z dowolnego kontrolera, mailera, nawet innego komponentu, wręczając jej wejścia.
-
Prawdziwa testowalność. To ta wielka. Ponieważ Cell jest obiektem, możesz ją zainstancjować i asercjować na jej wyrenderowanym wyjściu wprost, bez kontrolera, bez żądania, bez pełnostosowego testu integracyjnego:
it "pokazuje plakietkę eco dla produktów eco" do html = ProductCell.new(build(:product, eco: true)).() expect(html).to have_css(".badge", text: "Eco") endSzybkie, skupione testy jednostkowe dla twojego UI — rzecz, której model partial-plus-helper nie może dać.
-
Komponowalność. Cells renderują inne Cells, więc budujesz złożone UI z małych komponentów, każdy niezależnie zrozumiały i testowalny, dokładnie jak z komponentami frontu.
Kiedy po nie sięgać (a kiedy nie)
Zwykła ostrożność. Nie każdy partial musi być Cell — jednorazowy kawałek markupu używany w jednym miejscu jest w porządku jako partial, a owinięcie go w klasę komponentu to ceremonia. Sięgaj po komponenty widoku, gdy kawałek UI jest reużywany przez kilka widoków, gdy ma nietrywialną logikę prezentacji zasługującą na testy, albo gdy jest na tyle złożony, że jego ustawienie jest podatne na błędy. Karta produktu używana na pięciu stronach to idealny kandydat; jednolinijkowy partial nie. Jak przy obiektach usługowych i dekoratorach, celem jest dać strukturę tam, gdzie żyje złożoność, nie owijać wszystkiego odruchowo.
Dokąd to zmierza
Warto widzieć Cells jako część jasnego kierunku podróży. Model komponentowy sprawdził się na froncie, a ta sama logika stosuje się do renderowanego po stronie serwera UI: prawdziwa, testowalna, samodzielna jednostka bije fragment zależny od otaczającego stanu. Cells to dojrzała opcja w 2018, a idea zbiera dość rozpędu, że nie byłoby niespodzianką zobaczyć abstrakcję komponentu stającą się bardziej pierwszoklasową częścią samego Rails niedługo. Nauczenie się myślenia w komponentach widoku teraz — jawne wejścia, enkapsulowana logika, testowalne w izolacji — to trwała umiejętność, którakolwiek biblioteka ją niesie.
Werdykt
Domyślna warstwa widoku Rails — partiale oparte na zmiennych instancji, logika w helperach i szablonach — nie skaluje się do reużycia ani testowania, z tych samych powodów, dla których front porzucił ten styl. Komponenty widoku, przez Cells, czynią kawałek UI prawdziwym obiektem: enkapsulowanym, z jawnymi wejściami, komponowalnym i — co kluczowe — testowalnym jednostkowo bez kontrolera czy żądania. Używaj ich tam, gdzie UI jest reużywane albo logika jest nietrywialna na tyle, by zasłużyć na testy, i zostaw prosty jednorazowy markup jako partiale. Idea komponentu już przekształciła front; wniesienie jej do twoich renderowanych po stronie serwera widoków czyni je równie utrzymywalnymi i pozycjonuje cię pod to, dokąd architektura widoku Rails wyraźnie zmierza.