Ponieważ w międzyczasie RadRails został wchłonięty w środowisko Aptana, postanowiłem ten tutorial oprzeć właśnie o środowisko Aptana. Zarówno sposób tworzenia samego archiwum, jak i wyszukiwarki nie będzie się różnił od tego, co robiliśmy w RadRailsach. A jeśli jeszcze nie macie Aptana tutaj znajduje się poszerzony tutorial instalacyjny: Jak zainstalować środowisko Aptana oraz doinstalować Railsy.
Jeszcze słowo wyjaśnienia... RadRails jak pamiętacie miał angielskie nazwy. Środowisko Aptana, które zainstalowałem posiada natomiast spolszczone menu. A skoro tutorial piszę po polsku, to chciałbym pracować na polskiej wersji oprogramowania. W RadRailsach Aptany jeszcze nie wszystko zostało przetłumaczone, natomiast mam nadzieję, że z czasem się to zmieni :)
Archiwum tekstów to po prostu zwykła baza z tekstami. Utwórzmy zatem nowy projekt, nazwijmy go Archiwum. Nowy projekt tworzymy tak jak w RadRails:

Jeśli wszystko zrobiliśmy poprawnie i mamy poprawnie doinstalowane Railsy, powinny pojawić nam się pliki Railsowe w katalogu archiwum oraz na dole wyświetlić się zakładka konsoli:

Ponieważ chcemy pracować na Railsach otwórzmy sobie perspektywę taką jak w RadRails. Z menu wybieramy Okna -> Otwórz perspektywę -> Inne... i w oknie dialogowym wybieramy Rails:

Teraz już na pewno nikt nie będzie miał wątpliwości gdzie jesteśmy:

Zauważ, że nawet tytuł okna programu nie nazywa się już Aptana tylko Rails, ikonka niestety pozostała Aptana. Czerwoną elipsą zaznaczyłem 2 ikonki, które pojawiły się w prawej górnej części okna. Za ich pomocą będziemy mogli zmieniać środowisk Aptana na Rails i Rails na Aptana.
Teraz jeszcze pozostało jeszcze założyć w MySQL'u bazę archiwum_development i do pracy!
Do dzieła! W dolnym panelu wybieramy zakładkę Generatory i tworzymy migrację stworz_archiwum:

Edytujemy plik archiwum->db->migrate->001_stworz_archiwum.rb, by miał postać:
No i teraz należy odpalić migrację. Zatem zakładka Rake Tasks i wybieramy db::migration i Go!

Swoją drogą szkoda, że nie ma tłumaczenia... Rake po angielsku znaczy grabie, choć można spotkać i tłumaczenie dziwkarz (kulturalniej: hulaka). Jako czasownik to oczywiście grabić, ale w odniesieniu do broni: strzelać. Ponieważ strzelić migrację brzmi trochę pretensjonalnie, moja wersja autorskiego tłumaczenia będzie brzmieć odpalamy migrację (a cała zakładka mogłaby mieć nazwę, no nie wiem, może: Odpalacz zadań ;P).
Teraz jeszcze w pliku config/enviroment.rb odkomentowujemy 4 wiersze (49-51 i 54) i zmieniamy ox na tekst zaś en na y:
Co zrobiliśmy? Ustawiliśmy liczbę mnogą słowa tekst na teksty. Poprzednio robiliśmy to nieco prościej, tym razem użyliśmy dwóch osobnych metod. Jednej dla liczby pojedynczej i jednej dla mnogiej. Ponadto użyte tu zostały wyrażenia regularne. Co prawda nie jest to w tym wypadku użyteczne, nawet więcej - wyświetla się nam komunikat ostrzegający przed dwuznacznością. Jeśli ktoś się tym przejmuje to niech zrobi tak, jak to robiliśmy w poprzednich tutorialach.
W zakładce Generatory wybieramy szkielet (scaffold) i nazwijmy go tekst.

Skoro wygenerowaliśmy szkielet, to zobaczmy jak działa. W zakładce Serwery wybieramy nasz serwer i klikamy zieloną ikonkę Start:

Po kilku sekundach status serwera powinien zmienić się na Started, a my możemy obejrzeć nasz projekt w przeglądarce pod adresem: http://localhost:3000/teksty/.
Przy okazji skorzystamy z wewnętrznej przeglądarki w Aptana. Klikamy na ikonkę z Ziemią (podpisany Open browser) i w pasku adresowym wpisujemy adres naszego projektu:

Dodajmy kilka krótkich tekstów, tak byśmy później mięli w czym szukać :)
Mógłbym te kilka tekstów skopiować z jakiegoś portalu-gazety, ale w chwili obecnej, gdy zamyka się ludzi za tłumaczenie napisów do filmów, lepiej uważać z prawem autorskim, więc moja propozycja to: bajki Krasickiego. Jako że okres ochrony majątkowych praw autorskich wygasa po upływie 70 lat od śmierci autora, a Krasicki zmarł w 1801 roku, nie powinniśmy mieć problemów w naszym "policyjnym" państwie, nawet gdyby okres ochronny przedłużyli do 200 lat.
Ja dodałem następujące bajki:
Mamy już kilka tekstów w bazie, potrzebujemy zrobić wyszukiwarkę. Nie będzie to co prawda wyszukiwarka, która będzie radziła sobie z wymogiem Nielsena na wyszukiwarkę. Ale w zasadzie warunki, które postawił Nielsen, spełnia chyba tylko google. Mimo wszystko spróbujmy zrobić wyszukiwarkę i przy okazji czegoś się nauczyć. Od czego by tu zacząć...
Zróbmy na początek prosty formularz z jednym polem, do którego będziemy wpisywać szukane słowo (frazę). Formularz przekieruje dane na akcję, która wybierze nam z bazy danych pasujące do wpisanej frazy wyniki, a akcja będzie związana z widokiem o takiej samej nazwie (jak akcja), który wyświetli nam wyniki.
Formularz stworzymy w nowym pliku, zatem stwórzmy plik search.rhtml w katalogu views/teksty:

Edytujmy go do następującej treści:
Pierwszy wiersz mówi nam, że formularz będzie dane kierował do akcji live_search. W drugim wierszu wstawiamy tekst, aby był jakiś opis dla pola formularza, które tworzymy przy użyciu helpera text_field_tag, a pole to będzie zwracało swoją zawartość w zmiennej szukane.
Mamy już widok formularza app/views/teksty/search.rhtml:

Potrzebujemy utworzyć zatem akcję go obsługującą. W kontrolerze app/controllers/teksty_controller.rb tworzymy nową akcję (możemy dodać ją na końcu przed ostatnim end'em):
Co zrobiliśmy? Utworzyliśmy akcję live_search, która wpisuje do zmiennej fraza przesłaną przez formularz zmienną szukane. Mamy jeszcze wywołaną przy okazji dodatkową funkcję strip. Działa ona tutaj podobnie jak w PHP funkcja trim, czyli obcina z początku i z końca białe znaki (jeśli występują takowe w zmiennej szukane).
Instrukcja warunkowa if sprawdza, czy po obcięciu białych znaków poszukiwana fraza nie jest pusta (czy ktoś nie wpisał np. samej spacji albo może w ogóle pozostawił pole puste). Konstrukcja jest bardzo prosta - pytamy: czy nie pusta? I tak "czy" odpowiada ? (znak zapytania) na końcu wyrażenia, "nie" odpowiada ! (wykrzyknik) na początku, zaś "pusta" to po angielsku empty. Proste, co nie?
Jeśli szukana fraza nie jest pusta, tworzymy nową zmienną wynik do której zapiszemy wyniki zwrócone przez zapytanie SQL'owe. Do wyboru danych z tabeli MySQL używamy funkcji find z parametrami :all - bo chcemy dostać wszystkie kolumny z tabeli Teksty oraz :conditions - bo chcemy postawić konkretne warunki, które muszą spełnić wyszukiwane dane. U nas mamy prosty warunek, że treść tekstu ma spełniać warunek SQL'owy LIKE %fraza%, czyli dopasowanie tak, by gdzieś w tekście występowała dana fraza.
Jeszcze dygresja, z inspiracji 10
przykazaniami Railsów. Moglibyśmy użyć tutaj również funkcji find_by_sql:
@wynik = Tekst.find_by_sql( 'SELECT * FROM teksty WHERE tresc LIKE "%' + @fraza + '%"')
nie jest to jednak bezpieczne (aby to zabezpieczyć powinniśmy w takim
przypadku użyć dodatkowo metody ActiveRecord::Base.quote(), która
nie pozwoli na dodanie czegoś do kodu samego zapytania SQL). Przekazywanie
parametrów przez listę argumentów funkcji find jest wolne od tego
problemu (Rails'y same dbają o to, by zabezpieczyć dane), a zapis staje
się krótszy.
Po tym przydługawym opisie czas na dodanie widoku do utworzonej przed chwilą akcji. Tworzymy plik live_search.rhtml dokładnie tak, jak tworzyliśmy plik search.rhtml i edytujemy go do następującej postaci:
Co robi nasz widok? Jeśli (if) wynik jest pusty wypisuje, że nie znaleziono podanej frazy, w przeciwnym wypadku (else) dla każdego tekstu z uzyskanego wyniku wyświetla link do niego. Warto zwrócić uwagę na h przed zmienną fraza, jest to alias metody escape_html zabezpieczającej naszą stronę przed wyświetlaniem potencjalnie niebezpiecznych treści.
Zobaczmy jak to działa w zależności od tego co wpiszemy w Live search:
a) wpisujemy cokolwiek np. abc:

b) pozostawiamy pole puste albo wpisujemy spację:

c) wpisujemy ciąg znaków, który występuje (wpisałem szcz):

Tytuł bajki Słowik i szczygieł jest zaznaczony na czarno, gdyż w czasie robienia screena przesunąłem nad jej tytuł kursor myszy, tak by w pasku stanu pokazał się adres dokąd prowadzi ten link.
Pamiętacie regułę DRY, czyli po polsku NPS (nie powtarzaj się)? Czasem dłuższy kod, który będziemy częściej używać można skrócić do wywoływania go za pomocą funkcji. Zrobimy coś w tym stylu, to znaczy nasz link w widoku live_search.rhtml przerobimy na prostszy, a zarazem taki, który będzie przemawiał nawet do osoby nie znającej Rails'ów. Co zrobimy? Użyjemy helpera.
Tak nawiasem mówiąc, to samo link_to jest helperem, więc zrobimy helper z wykorzystaniem helpera. Otwórzmy plik app/helpers/teksty_helper.rb i dodajmy do niego następujący kod:
Teraz zamiast naszego linku w pliku live_search.rhtml możemy wstawić tekst_url(tekst), co wpisze nam adres URL (co tu bynajmniej nie znaczy Ukraińska Republika Ludowa, lecz zunifikowany format adresowania zasobów) wybranej bajki. Zmieniony plik live_search.rhtml ma zatem postać:
Łatwo się mówi, ale prosto nie będzie. Nie będzie też trudno, ale jednak trochę nam się pokomplikuje, być może dlatego, że ja nie umiem inaczej - ktoś ma inny pomysł, czekam na sugestie!
W skrócie, jak chcemy by nasza wyszukiwarka działała: po modyfikacji pola szukane w formularzu, wyszukiwarka pod polem formularza ma pokazać wyniki dla aktualnej treści pola. Dodatkowo warto by było aby w trakcie poszukiwań wyświetlał się jakiś znaczek, że trwają poszukiwania. Znaczek taki przeważnie nazywa się spinner.
W tym celu modyfikujemy plik app/views/teksty/search.rhtml:
Co dodaliśmy? Dodaliśmy kod dla obrazka spinner.gif za pomocą
wbudowanego helpera image_tag. Helper ten wygeneruje nam zwykły
kod HTML dla obrazka:
<img alt="spinner" id="spinner" src="../../images/spinner.gif" style="display:none"
/>.
Styl display: none sprawi, że po załadowaniu strony obrazek nie będzie
wyświetany.
Następnie wstawiliśmy observe_field - wygeneruje nam to kod Ajax'owy
niezbędny dla naszego mechanizmu live search. Jako pierwszy parametr
funkcja przyjmuje nazwę pola, które będziemy sprawdzali. Pozostałe parametry
to:
:frequency - po jakim czasie od ostatniej modyfikacji pola ma
zacząć się (u nas:) wyszukiwanie,
:update - jaki element ma zostać zaktualizowany,
:loading - co ma się dziać w trakcie asynchronicznego ładowania,
:complete - co ma się dziać po załadowaniu,
:url - jaki adres załadować (u nas: wywołać akcję live_search).
Na koniec dodaliśmy jeszcze pole wyników (div z id wyniki).
Ach! I nie zapominajmy o obrazku! Przecież jeszcze nie dodaliśmy go nigdzie, więc odnajdujemy katalog naszego projektu. U mnie C:/aptana/archiwum/ i do katalogu public/images kopiujemy nasz obrazek spinner.
Aby to nam zadziałało musimy dołączyć biblioteki Javascriptu dla Ajax'a. W tym celu w layoucie app/views/layouts/teksty.rhtml zaraz za tagiem stylesheet_link_tag dodającym arkusz stylów do naszego dokumentu dopiszemy 2 tagi dołączające biblioteki (wszystko w sekcji head):
Teoretycznie wszystko powinno działać, w końcu akcja live_search w kontrolerze ma takie samo zadanie. Tutaj jednak właśnie rzeczy się komplikują (jak pisałem na początku tego kroku). Problem mam z przekazaniem parametru :szukane poprzez observe_field i przejęcie go w kontrolerze. Ponieważ nie wiem jak to rozwiązać, do czasu gdy nie znajdę lepszego wyjścia, obejdziemy ten problem inaczej.
Poniższe rozwiązanie NIE JEST zalecane, ale z braku laku lepsze to niż nic!
Dokonamy zmiany w kontrolerze app/controllers/teksty_controller.rb. Dotychczasowy kod: @fraza = params[:szukane].strip zmienimy na:
Teraz w pierwszym wersie żywcem pobieramy to co możemy. Ponieważ pobraliśmy w ten nieelegancki sposób, na końcu dopisywany jest znak równości, który brutalnie usuwamy w drugim wersie (przy okazji obcinając białe znaki).
Uruchamiamy http://localhost:3000/teksty/search/ i powinno działać:

Oprócz działania możemy także dodać do naszej wyszukiwarki kilka efektów. Przykładowe efekty znajdziemy na stronie: http://script.aculo.us/.
Pozostaje nam najpierw zalinkować blibliotekę efektów do naszego projektu. Tak jak w poprzednim kroku w pliku app/views/layouts/teksty.rhtml za tagami javascript_include_tag umieszczamy kolejny:
Pozostaje nam dodać efekty, więc w pliku app/views/teksty/search.rhtml w observe_field modyfikujemy parametr :complete, do następującej postaci:
spowoduje to, że znalezione wyniki będą nam się rozwijały w dół, zaś spinner po znalezieniu wyników nie będzie nagle znikał, tylko stopniowo zanikał:

Koniec i bomba, a kto czytał ten trąba!