W tym tutorialu dowiesz się jak używać walidacji danych z formularzy oraz wyrażeń regularnych, a także gdzie wstawić swój layout i jak zrobić stronicowanie. Do tego wszystkiego konieczne będą umiejętności nabyte z tutorialu pierwszego.
Naszą drugą aplikacją webową będzie księga gości, w której każdy odwiedzający nasze strony będzie mógł zapisać komentarz dotyczący np. naszego serwisu, a ponadto pozostawić swoje dane jak imię, nazwisko, e-mail, czy adres strony www (jeśli posiada). Brzmi całkiem prosto i takie też będzie po lekturze tego tutorialu. :)
Tak jak w tutorialu pierwszym na początek utworzymy nowy projekt o nazwie projekt2 oraz tworzymy dla niego bazę danych o nazwie projekt2_development.
Tworzymy migrację (zakładka Generators, wybierz migration) o nazwie stworz_wpisy. Gdy zostanie utworzona, edytujemy plik db/migrate/001_stworz_wpisy.rb, aby wyglądał jak na listingu poniżej:
Pojawiły się nam nowe elementy takie jak:
:limit - ograniczenie długości Stinga
:null - czy pole może przyjąć wartość NULL (czyli po ludzku:
być puste)
Zakładamy, że obowiązkowe pola w naszym formularzu to imie, mail i
tresc, dlatego dla nich parametrowi :null przypisujemy wartość false,
co oznacza, że baza danych będzie ich zawsze wymagała. Gdy utworzyliśmy
plik migracji możemy ją wykonać (zakładka Rake Tasks, wybierz db:migrate).

Zanim utworzymy szkielet obsługi naszego wpisu, musimy utworzyć regułę, mówiącą, że liczba mnoga od wpis to wpisy. A zatem jak zapewne pamiętacie edytujemy fragment pliku config/enviroment.rb jak na listingu poniżej:
I skoro jesteśmy przy języku polskim, to ustawmy jeszcze kodowanie (klikamy dwa razy na projekcie, z menu kontekstowego Properties, w oknie Properties for projekt2 ustawiamy Text file encoding na UTF-8).
Generujemy szkielet obsługi wpisu (zakładka Generators, wybieramy scaffold, a w polu tekstowym wpisujemy nazwę: wpis).

Uruchom serwer (zakładka Servers, kliknij Start). W dowolnej przeglądarce internetowej wpisz adres: http://localhost:3001/wpisy/, następnie kliknij New wpis i nie wypełniając formularza kliknij Create.

Ups... Możemy wpisać pustą notkę i ją zapisać! No to jak czegoś takiego używać?! W kolejnych kilku krokach postaramy się jakoś temu zaradzić.
Na początek kilka słów teorii, ale przyda się ona zarówno dla tego, jak i kolejnych tutoriali. W Railsach aplikacje tworzy się według wzorca projektowego Model-View-Controller (Modele, Widoki, Kontrolery). Źródła aplikacji znajdują się w katalogu app i są zorganizowane w sposób odzwierciedlający ten podział:
W tym tutorialu poznasz podstawowe zastosowanie wszystkich trzech wymienionych komponentów (kontrolerów, modeli i widoków). W Railsach istnieją jeszcze helpery (helpers), które definiują funkcje pomocnicze dla widoków, ale o nich dowiesz się więcej w kolejnym tutorialu.
Na pierwszy ogień pójdą modele. Zatem otwórzmy jedyny (mamy jedną tabelę z naszymi danymi w bazie) istniejący model naszej aplikacji app/models/wpisy.rb i dopiszmy następujący wiersz:
Zapisujemy, a następnie sprawdzamy, co teraz się stanie, gdy będziemy dodawać pusty wpis.

Powinno wyświetlić nam komunikat, że nie można dodać pustego wpisu. Funkcja validates_presence_of sprawdza nam bowiem, czy użytkownik wprowadził dane w danym polu formularza.
Zróbmy Railsom na złość i wpiszmy cokolwiek w treści, a także wpiszmy superdługie imię! Nierealne? To w ramach relaksu poczytaj w Wikipedii o pewnym szwedzkim dziecku z niezwykłym imieniem. Załóżmy jednak, że sąd był bardziej liberalny i zgodził się na to nieco ekstrawaganckie imię. Skopiujmy je i wklejmy do formularza.
No teraz to popsuło na całego, ale przynajmniej nie zapisało do bazy.
Co się stało i dlaczego? Skoro w kroku drugim parametrem :limit wyznaczyliśmy graniczną długość imienia w bazie, oznacza to, że miało to dla nas jakieś znaczenie. Zatem gdyby wpisana treść była przykładowo szkodliwa, mogłaby w skrajnym przypadku zrobić z bazą coś, co spowodowałoby na przykład czasowe zawieszenie obsługi serwisu, a tego byśmy przecież nie chcieli.
Dostaliśmy informację o błędzie z bazy danych, ale wyświetliło ją w niezbyt przyjazny sposób. Musimy to zatem naprawić.
Aby wprowadzić walidację ograniczenia długości pola wystarczy użyć parametru :maximum. Użyjemy go zarówno dla pola imię, jak i nazwisko, mail i strona. Edytujmy dalej plik app/models/wpisy.rb:
Sprawdźmy co teraz się stanie przy superdługim imieniu.

Nie puścił, ok. No to skróćmy imię i wpiszmy w polu mail cokolwiek np. abc... Ech... Znowu nieciekawie... Widział ktoś kiedyś adres e-mail abc? Jeszcze żeby chociaż jakaś małpa tam się pojawiła, a tu tylko trzy literki. No tylko jak sprawdzić, czy mail jest mailem?
Nie spotkaliście się z tym? Nie uwierzę, ale wytłumaczę. Wyrażenia regularne to pewne wzorce (szablony) opisujące łańcuchy znaków. Wyrażenia regularne mogą określać zbiór pasujących łańcuchów, mogą również wyszczególniać istotne części łańcucha. Brzmi skomplikowanie, ale wcale takie nie jest. Na początek tabelka (nie martw się - nie cała będzie nam potrzebna i nie trzeba jej umieć na pamięć, ale warto wiedzieć gdzie ją znaleźć, gdy układamy wyrażenie regularne):
[]
|
zakres (np. [a-z] oznacza literę z zakresu od a do z)
|
\w
|
litera lub cyfra, to samo co: [0-9A-Za-z]
|
\W
|
znak inny niż litera lub cyfra |
\s
|
biały znak (odstęp), to samo co: [ \t\n\r\f]
|
\S
|
znak inny niż biały znak (nie odstęp) |
\d
|
cyfra, to samo co: [0-9]
|
\D
|
znak inny niż cyfra |
\b
|
backspace (0x08) |
^x
|
różne od x |
.
|
dowolny znak |
*
|
zero lub więcej powtórzeń (dowolna ilość powtórzeń) |
+
|
jedno lub więcej powtórzeń |
{m,n}
|
co najmniej m i co najwyżej n powtórzeń |
?
|
najwyżej jeden raz, to samo co: {0,1}
|
|
|
odzielnik alternatywy wykluczającej (albo) |
()
|
grupowanie znaków |
^
|
asercja (kotwica) początku wiersza |
$
|
asercja (kotwica) końca wiersza |
\b
|
asercja (kotwica) odstępu między wyrazami |
\B
|
dopełnienie asercji \b |
Ruby wspiera także wyrażenia modyfikatorami, tym którego użyjemy będzie modyfikator /i, który sprawia, że wyrażenie nie zwraca uwagi na wielkość liter (mała ciekawostka związana z "i" - jeśli używaliście kiedyś PHP, to tam także wystarczy dopisać "i" na końcu funkcji ereg, by nie zwracała ona uwagi na wielkość liter).
No to tyle teorii, zróbmy coś praktycznego. Spróbujmy dopasować nazwę pliku graficznego. Niech możliwe rozszerzenia w naszym przykładzie będą: gif, jpg, png. Zatem możliwe jest jedno z nich: (jpg|png|gif). Ponieważ jest to końcówka nazwy i ma wystąpić tylko raz możemy dodać znak dolara i jedynkę w klamrach: (jpg|png|gif){1}$. Przed rozszerzeniem ma być nazwa złożona z cyfr, liczb, znaku "-" lub kropek (ponieważ kropka w wyrażeniu regularnym jest dowolnym znakiem, aby podkreślić, że chodzi nam o kropkę jako znak dajemy przed nią backslash), zatem całość będzie wyglądać następująco:
(([-a-z0-9]+\.)+(jpg|png|gif){1})$.
Aby takie wyrażenie zastosować w Ruby należy je ująć w slashe, a ponieważ nie zależy nam na wielkości liter w nazwie pliku, dodamy jeszcze modyfikator i ostatecznie nasze wyrażenie będzie wyglądać tak:
/(([-a-z0-9]+\.)+(jpg|png|gif){1})$/i.
Do sprawdzania dopasowania łańcucha do wyrażenia w języku Ruby służy operator =~. Zwraca on pozycję w łańcuchu gdzie dopasowanie zostało znalezione albo nil jeśli wzorzec wyrażenia nie pasuje do dopasowywanego łańcucha. Aczkolwiek my nie będziemy używać operatora dopasowania, zamiast tego użyjemy funkcji validates_format_of.
Wyrażenia regularne przydają się do różnych rzeczy i warto je znać, ale z mailem nie będziemy kombinować. Choć pozornie adres e-mail to trochę liter i jedna małpa, naprawdę struktura adresu e-mail jest bardzo skomplikowana. Dlatego nie będziemy sami tworzyć wyrażenia, tylko skorzystamy z gotowca. Skąd go wziąć?
Z pomocą przyjdzie nam Ruby Interactive Reference, czyli system pomocy RI (aby polski skrót zgadzał się z angielskim odpowiednikiem, możemy go nazwać Ruby Informacja).
Ruby Informacja (RI) jest standardową zakładką w tym samym panelu, gdzie zakładka Servers (w razie czego: Window->Show View->Other... i z drzewa wybieramy Ruby->RI).
Ponieważ jeszcze z niej nie korzystaliśmy prawdopodobnie potrzebna będzie
konfiguracja. Wybierz Window->Preferences, następnie z drzewa wybierz
Ruby->RI/rdoc i wpisz ścieżki do plików ri i rdoc, domyślnie są to:
RDoc path: C:\ruby\bin\rdoc
Ri path: C:\ruby\bin\ri
Kliknij Apply, a następnie OK.

Przy okazji warto skonfigurować interpreter Ruby, w tym samym oknie
z drzewa wybierz Ruby->Installed Interpreters, przyciskiem Add
dodaj interpreter języka Ruby:
Interpreter Name: ruby
domyślna lokalizacja to:
Location: C:\ruby\bin\ruby.exe
Kliknij OK w okienku dialogowym i OK w oknie Preferences,
by powrócić do programu.

Teraz już możesz przejść do zakładki Ruby Informacja (RI) (jeśli nadal nic nie widzisz spróbuj odświeżyć klikając na dwie złote strzałki z prawej strony). Aby wyszukać wpisz w polu tekstowym: validates_format, powinno znaleźć jeden rekord. Kliknij na niego, powinien otworzyć się w okienku z prawej strony.
Skopiuj całe polecenie validates_format_of z Ruby Informacji i wklej do naszego kodu, tak by otrzymać to co poniżej:
Jak pewnie zauważyłeś dodaliśmy też wiadomości (:message). Zawartość parametru :message będzie pokazywana w chwili, gdy nie powiedzie się dopasowanie.
Spróbuj wymyślić jak będzie wyglądało wyrażenie regularne dla adresu strony www. Na przykład adres strony tych tutoriali to: http://rubyonrails.kluza.eu - i jak to zapisać?
Pomyślmy i spróbujmy to analizować po kolei. Od lewej chcemy http (ale niektórzy to pomijają, zatem 0 lub 1 raz, możemy użyć ?), potem 2 slashe // musimy użyć backslashy, aby było poprawnie, gdyż slash jest znakiem specjalnym. Następnie część dość dowolna składająca się z liter, cyfr lub znaku "-". A na końcu domena złożona z co najmniej dwóch liter. Jeśli wymyśliliście coś, możecie spróbować wstawić walidację do naszego modelu. Poniżej jedna z możliwości:
Pewnie zauważyliście nowy wers z parametrem :if. Jest to wers warunkowy i mówi, kiedy ma odbywać się walidacja (jakie warunki muszą być spełnione, żeby w ogóle funkcja walidacyjna zadziałała). W naszym przypadku podajemy swoistą konstrukcję języka Ruby z operatorem znak zapytania "?". Operator ten zwraca informację, czy zmienna :strona istnieje, tzn. czy w formularzu użytkownik wypełnił rubrykę dotyczącą jego strony www. Jeśli tak, walidacja się wykona, w przeciwnym wypadku walidacja ta zostanie pominięta.
Po co taka konstrukcja? Gdybyśmy nie podali jej, funkcja walidująca zawsze sprawdzałby zmienną :strona z formularza i gdyby była pusta, zwracałaby błąd. A na początku tego tutorialu ustaliliśmy, że pole strona w formularzu jest nieobowiązkowe, bo nie każdy ma stronę internetową
Patrzymy na ten formularz i co rzuca się w oczy? Możemy wybrać datę... no pięknie, ale jeśli wpis ma miejsce dziś to nie chcemy przecież, żeby ktoś wybrał datę sprzed dwóch lat. No to nie dajmy możliwości wyboru daty! Usuwamy zatem z pliku app/views/wpisy/_form.rhtml dwa przedostatnie wiersze (nr 19 i 20):
Póki co usunęliśmy możliwość wyboru daty, więc nasze wpisy zostaną stworzone bez daty, a przecież nie tego chcieliśmy. Musimy zatem w kontrolerze akcji dodawania nowego wpisu dodać dzisiejszą datę. Co to jest kontroler mówiliśmy w kroku szóstym, zatem możemy go otworzyć (app/controllers/wpisy_controller.rb) i zająć się edycją akcji tworzącej nowy wpis do księgi gości z danych pobranych z formularza (akcja create):
Jak zauważyliście, dodaliśmy tylko jeden wers (numer wersu 25), który powoduje wpisanie do zmiennej o nazwie data z naszego wpisu dzisiejszej daty (funkcja Date.today).
Teraz pozostaje nam jakoś estetycznie wyświetlić wpisy. Zapewne wiecie jak wyglądają księgi gości, my nie będziemy robić nic niestandardowego, bo przecież nie o layout nam chodzi, a o ideę. Listing wpisów, który mamy pod adresem: http://localhost:3001/ wpisy/list księgi gości raczej nie przypomina. Ale nie ruszajmy go, bo gdyby tylko wprowadzić autoryzację (będzie w którymś z kolejnych tutoriali), to mamy panel administracyjny. A my tymczasem zróbmy nową akcję o nazwie ksiega.
Otwórz do edycji kontroler wpisów: app/controllers/wpisy_controller.rb i skopiuj i wklej drugi raz akcję list. Nie usuwaj jej, tylko skopiuj i wklej. Dwie akcje list nam przecież nie potrzebne, więc nazwij tę drugą ksiega. Chcemy teraz, by akcją wyświetlaną po wejściu pod adres http://localhost:3001/wpisy, była akcja księga, zatem modyfikujemy akcję index. Na listingu poniżej, to co zmieniliśmy w kontrolerze wpisów:
Co robi teraz akcja index? Najpierw wywołuje naszą nową akcję ksiega, a następnie renderuje ją (wyświetla) poleceniem render :action.
Zdefiniowaliśmy akcję księga w kontrolerze, ale to nie wystarczy. Skoro ją renderujemy, to potrzebujemy zdefiniować widok, bo przecież do wyświetlania służą widoki. Musimy więc utworzyć widok ksiega.rhtml. Kliknij prawym klawiszem myszy na katalogu app/views/wpisy i z menu kontekstowego wybierz New->File. Następnie w nazwie pliku (File name) wpisz ksiega.rhtml i kliknij Finish. Nowy plik powinien zostać utworzony.
Plik księga.rhtml będzie bardzo podobny do list.rhtml. Główną różnicą będzie to, że usuniemy możliwość pokazywania, edytowania i usuwania pojedynczych wpisów. W końcu księga gości nie ma służyć gościom do edycji czy usuwania wpisów. Ponadto na dole księgi gości dodamy formularz nowego wpisu, tak by nie trzeba było przechodzić na kolejną stronę, by dodać nowy wpis. Nie wiem, czy to najlepszy pomysł z punktu widzenia użyteczności, ale dzięki temu dowiecie się jak renderować części (:partial), ale na dobre zaczniemy ich używać dopiero w kolejnym tutorialu. Wracając do naszej księgai - najpierw listing (app/views/wpisy/ksiega.rhtml), a potem po krótce wyjaśnię, co zawiera ten plik:
Kodu HTML nie będę objaśniał, gdyż jak sądzę każdy kto zabiera się za RadRailsy ma o HTMLu mniejsze lub większe pojęcie. Mam jednak nadzieję, że większe :) Co natomiast jest takiego Railsowego w naszym kodzie...
Znaczniki Railsowe zaczynają się <% i kończą %>.
Znaczniki <% for wpis in @wpisy %> i <% end %> tworzą nam pętlę,
która iteruje kolejne wpisy. I dla kolejnych wpisów wyświetlamy w kolejnych
kolumnach tableli:
<%=h wpis.imie + ' ' + wpis.nazwisko %> - imię i nazwisko,
<%=h wpis.data.day.to_s + '.' + wpis.data.month.to_s + '.' + wpis.data.year.to_s
if wpis.data %> - datę, jęśli istnieje.
A także treść wpisu: <%=h wpis.tresc %>
Polecenie h przed każdą z wyświetlanych wartości służy do oczyszczenia
tych wartości z rzeczy potencjalnie niebezpiecznych, jak znaczniki kodu
HTML, Javascript itd.
Następna część kodu jest bezpośrednio skopiowana z pliku new.rhtml, która tworzy nam formularz. Jedyną rzeczą, na którą warto zwrócić uwagę to komenda render :partial, która w odróżnieniu od render :action (która renderowała nam akcję) doda nam do naszej strony wyrenderowaną część (:partial) o nazwie form. Widoki odpowiadające akcjom mają nazwy takie jak akcja z nimi związana. Natomiast widoki części (:partial) mają nazwy _nazwa, w naszym przypadku ponieważ renderujemy część (:partial) form, spodziewać się możemy pliku _form (z rozszerzeniem jak każdego widoku: .rhtml).
Teraz pozostaje nam dodać plik kaskadowych arkuszy stylów CSS. Najpierw w głównym pliku naszego layoutu dodamy informację, że dołączamy dodatkowy plik CSS. Zatem w pliku app/views/layouts/wpisy.rhtml w piątym wierszu dopisujemy informację do dodaniu pliku stylów o nazwie ksiega.
Plik ten będzie dodawany warunkowo, jeżeli nazwą kontrollera wywołanego jest księga, czyli po prostu, dodawać go będzie tylko wtedy, gdy będziemy przeglądać księgę, a nie np. gdy otworzymy listę wpisów (http://localhost:3001/wpisy/list). Pozostaje nam teraz jedynie utworzyć plik ksiega.css w katalogu public/stylesheets (prawym klawiszem klik na katalogu, New->File). Style CSS na pewno doskonale znacie, więc możecie je napisać. Poniżej zamieszczam przykładowy listing:
I dla takich stylów nasza księga będzie wyglądać tak:
Skoro mamy krok 13, to wyobraźmy sobie coś pechowego... na przykład nasza strona stała się niezwykle popularna i do naszej księgi wpisało się 1000 osób! I to ma być pech? No dla kogoś, kto chce obejrzeć te 1000 wpisów to raczej tak, taka strona będzie się długo ładowała, a potem będą trudności z przewijaniem niewielkim suwakiem.
Stronicowanie czy też czasem używana nazwa stronowanie to dzielenie na strony. Zazwyczaj robi tak google przy wyszukiwaniu. Gdy przeszukujemy sklepy czy aukcje internetowe, wtedy także mamy zbyt wiele wyników wyszukiwania stąd pomysł, aby te wyniki wyświetlać partiami. Ale nawet gazety z dłuższymi artykułami dzielą je na kilka części.
No to wpadłem... Zaraz zapytacie, czemu ja nie dzielę tutoriali na strony. Już odpowiadam. Dla kogoś kto czyta tutorial jako całość jest to mniej wygodne, gdy musi czekać, aż kolejne części się załadują, a poza tym gdyby ktoś chciał wydrukować tutorial i czytać go na papierze, o wiele łatwiej będzie mu to drukować, gdy ma wszystko w jednym kawałku. No dobra, ale koniec tłumaczenia z mojej strony i bierzmy się do pracy.
W zasadzie mógłbym napisać jedno zdanie i skończyć: RadRails domyślnie stronicuje. Ale nie jestem taki małostkowy i wyjaśnię. Jeśli przybędzie nam więcej wpisów (dodaj kilkanaście do naszej księgi gości), RadRails automatycznie podzieli nam listę na strony. Dokładniej pokaże 10 wpisów na stronę. A co gdybyśmy chcieli inaczej?
(dopisz tutaj info nt parametrow :limit i :offset)
+ kolejność :order
Koniec i bomba, a kto czytał ten trąba.