You are on page 1of 60

Uníwersytet ím.

Adama Míckíewícza
Wydzíal Matematykí í Informatykí
Robert Pankoweckí
Iaroslaw Píebanskí
Tworzenie i wdrazanie
aplikacii internetowych na
platformie Ruby on Rails
Praca magísterska
napísana pod kíerunkíem
prof. dr. ∀ab. Zbígníewa Paíkí
Poznan ∃009
5erdeczne podziekowania dla
Pana prof. dr. hab. Zbigniewa Palki
za poswiecony czas i nieoceniona
pomoc w opracowaniu niniejszej pracy.
3
Spis treści
Wstęp ................................................................................................................ 4
1. Rozwój platformy Ruby On Rails .................................................................. 6
1.1. Historia Ruby ...................................................................................... 6
1.2. Ruby on Rails ...................................................................................... 7
2. Koncepcje i techniki programistyczne ........................................................... 9
2.1. Koncepcje ............................................................................................ 9
2.2. Techniki ............................................................................................. 10
3. Podstawy języka Ruby ................................................................................ 15
3.1. Elementy składni .............................................................................. 15
3.2. Programowanie obiektowe ................................................................ 16
4. Wielowarstwowa architektura systemu ....................................................... 19
4.1. Czym jest Ruby on Rails ................................................................... 19
4.2. Przypływ sterowania i model MVC .................................................... 21
4.3. Narzędzia .......................................................................................... 22
4.4. Komponenty ...................................................................................... 23
5. Budowa aplikacji ......................................................................................... 25
5.1. Kontroler ........................................................................................... 25
5.2. Model ................................................................................................ 28
5.3. Widok ................................................................................................ 34
6. Testowanie aplikacji .................................................................................... 36
6.1. Wprowadzenie ................................................................................. 36
6.2. Testy jednostkowe ........................................................................... 40
6.3. Testy funkcjonalne ........................................................................... 41
6.4. Inne rodzaje testów ......................................................................... 45
6.5. Narzędzie alternatywne oraz dodatkowe ......................................... 46
7. Wdrożenie ................................................................................................... 48
7.1. Dostępne opcje ................................................................................ 48
7.2. Platforma wdrożeniowa .................................................................... 51
8. Wnioski ...................................................................................................... 57
Bibliografia ...................................................................................................... 59
4
Wstęp
Ruby on Rails jest znaną w świecie i dojrzałą technologią, która od lat rozwija
się w zawrotnym tempie, zdobywając sobie coraz więcej zwolenników. Jej sukces
w  sporej mierze przyczynił się do popularyzacji języka Ruby, który w 2006
bardzo szybko osiągnął 10 miejsce w rankingu Tiobe, a mimo to znajomość
tych rozwiązań w środowisku akademickim wydaje się być bardzo niewielka.
W  niniejszej pracy postaramy się w ogólnym zarysie zaprezentować cały
framework, posiłkując się przy tym fragmentami kodu zrealizowanej przez nas
aplikacji.
Praca zaczyna się przedstawieniem krótkiej historii Rails oraz języka, w którym
został on napisany, co pozwala lepiej zrozumieć kontekst jego powstawania oraz
porównać na tle innych technologi tego okresu.
Sam framework został opublikowany w 2004 roku, trzy lata po tym jak pojawił się
Manifest Zwinnego Wytwarzania Oprogramowania (Agile Manifesto) i od początku
wspiera pracę z użyciem technik, wykorzystywanych głównie w metodykach
zwinnych. Z tego względu drugi rozdział poświęcony został na ich opisanie oraz
wspomnienie kluczowych koncepcji, jakie kryją się za jego architekturą.
Ruby posiada bardzo duże możliwości, które okazują się być szczególnie
przydatne w metaprogramowaniu - technice mającej bardzo duże znaczenie
w  Rails. Jednak już nawet na poziomie składniowym odróżnia się on nieco
od najpopularniejszych obecnie języków, dlatego trzeci rozdział stanowi krótkie
omówienie samego języka, dzięki czemu mamy nadzieję, że zrozumienie
przykładów zawartych w pracy nie będzie nastręczało problemów.
Czwarty rozdział to omówienie architektury systemu. Daje on pogląd na to, jak
ogólnie działa aplikacja i jak poszczególne warstwy współpracują ze sobą, podczas
interakcji użytkownika ze stroną internetową. Każda z warstw modelu MVC jest
scharakteryzowana osobno w części piątej.
Istotnym elementem procesu wytwarzania każdego rodzaju oprogramowania
jest testowanie. Rails dostarcza szereg narzędzi wspierających automatyczne
testowanie, które dodatkowo mogą zostać uzupełnione wieloma bibliotekami
stworzonymi przez społeczność programistów. Jedne jak i drugie zostały
przedstawione w rozdziale szóstym.
Siódmy rozdział skupia się na wadach i zaletach dostępnych rozwiązań, z których
można skorzystać podczas uruchamiania gotowego systemu. Zostały w nim
także szczegółowo opisane nasze doświadczenia oraz technologie użyte podczas
wdrażania projektu w domenie co-do-grosza.pl w kwietniu 2009 roku.
W ostatnim rozdziale znalazły się wnioski oraz rozważania odnośnie przyszłości
platformy.
Zrealizowana w ramach pracy magisterskiej aplikacja internetowa Manage My
Money ma za zadanie wspierać użytkowników w procesie zarządzania finansami
osobistymi. Pozwala ona wprowadzać oraz importować informacje o dokonanych
przez użytkownika transakcjach i ich przeznaczeniu, w celu ich późniejszego
przetwarzania. Stworzony system posiada moduły odpowiedzialne za zarządzanie
Wstęp
5
dłużnikami i wierzycielami (wysyłanie przypomnień), planowanie (realizacja
krótko i długoterminowych celów finansowych), raportowanie (różnorodne
wykresy) oraz obsługuje wiele rodzajów jednostek (akcje, fundusze, waluty).
6
Rozdział 1
Rozwój platformy Ruby On Rails
We współczesnym świecie używamy stron internetowych pełnych treści oraz
serwisów, które pozwalają robić, co tylko wyobraźnia nam podpowie. Jednakże
droga do stanu jaki obserwujemy teraz była długa i wyboista - kilkanaście
lat minęło od powstania pierwszej strony internetowej której treść została
wygenerowana dynamicznie. Ruby on Rails dopiero pojawił się na tej ścieżce
i jest wielce prawdopodobne że innowacyjność rozwiązań tego frameworku stanie
się kamieniem milowym w dalszym rozwoju technologii internetowych. Poniższy
rozdział stanowi zwięzły opis historii języka Ruby oraz frameworku Ruby on Rails.
1.1. Historia Ruby
Projekt Ruby został zainicjowany w 1993 roku przez Yukihiro Matsumoto,
japońskiego programistę. Celem Matsumoto było stworzenie „języka
skryptowego, który jest potężniejszy niż Perl i bardziej zorientowany obiektowo niż
Python”[3]. Pierwsza publiczna wersja o numerze 0.95 została zaprezentowana
na Japońskich grupach dyskusyjnych w 1995 r. Już w tym stadium rozwoju język
posiadał duże możliwości: pełną obiektowość, mixiny, iteratory, domknięcia,
przechwytywanie wyjątków czy odśmiecacz pamięci (garbage collection). Wersja
1.0 została opublikowana rok później, a w 1999 wraz z wersją 1.3 i powstaniem
anglojęzycznej listy dyskusyjnej języka zaczęła się ekspansja Ruby'ego poza
Japonię.
Język Ruby sam w sobie nie zyskał popularności przy tworzeniu stron
internetowych, choć było to naturalnie możliwe, np. przy użyciu mechanizmu
Common Gateway Interface[1]. CGI w prosty sposób pozwala serwerowi
internetowemu na uruchomienie skryptu (przykładowo w języku Ruby)
w  odpowiedzi na przychodzące żądanie i wysłanie wyników jego działania jako
odpowiedź. Na długie lata Ruby pozostał jednak niszowym narzędziem używanym
np. przez administratorów do pisania skryptów powłoki w systemach uniksowych.
Dopiero użycie go przez framework RoR zwróciło uwagę programistycznego
świata na ten egzotyczny język.
Obecnie Ruby cały czas intensywnie się rozwija, aktualna w czasie pisania tej
pracy wersja stabilna języka (1.8.7) została wydana w czerwcu 2008 roku, tej
wersji zaleca się do pracy z frameworkiem Ruby On Rails. Dzięki RoR nastąpiło
lawinowe zainteresowanie tym językiem, wg. indeksu TIOBE (www.tiobe.com)
Ruby jest obecnie 10. najpopularniejszym językiem programowania.
Rozwój platformy Ruby On Rails
7
Rysunek 1.1. Wykres zainteresowania językiem w ostatnich latach
Aktualne prace nad językiem mają na celu głównie zwiększenie wydajności
interpretera oraz ustandaryzowanie składni języka w ISO. Równolegle powstaje
kilka interpreterów języka Ruby[2]:
• MRI - standardowa implementacja, rozwijana i utrzymywana przez Yukihiro
Matsumoto, aktualna wersja 1.8.
• Yarv - implementacja oparta o maszynę wirtualną, pozwala na nawet 10-
krotne zwiększenie wydajności kodu, Yarv stał się oficjalnym interpreterem
Ruby w  wersji 1.9. Istnieje możliwośc uruchomienia Ruby on Rails na tej
implementacji.
• Ruby Enterprise - poprawiona wersja MRI, o szybszym działaniu interpretera
i mniejszym zużyciu pamięci - zalecana do środowisk produkcyjnych.
• jRuby - będąca implementacją działającą na maszynie wirtualnej Java.
Dystrybucja ta jest bardziej przenośna oraz umożliwia dostęp do wszystkich
bibliotek napisanych w języku Java z poziomu aplikacji Ruby. Ma to duże
znaczenie dla popularyzacji języka w dużych komercyjnych projektach, których
domeną jest w tej chwili Java i technologie J2EE.
• IronRuby - implementacja ma platformę .NET (trwają prace).
• MacRuby - implementacja w języku ObjectivC dla systemu Mac OS X (trwają
prace).
1.2. Ruby on Rails
W ciągu wielu lat od powstania pierwszego skryptu CGI powstały różnorodne
narzędzia do tworzenia stron w liczących się językach programowania.
W  małych i  średnich zastosowaniach dużą popularność zyskał PHP, pozwalał
szybko tworzyć strony przeplatając kod HTML instrukcjami dostępu do bazy
danych. W rozwiązaniach korporacyjnych dużą popularność zyskała Java,
Rozwój platformy Ruby On Rails
8
najpierw wprowadzając specyfikację Java Servlet, a potem Java Server Pages.
Szybka ekspansja internetu sprawiła że początkowe technologie stały się
niewystarczające, powstawały coraz to nowe rozwiązania. Programista WWW
chcący stworzyć rozbudowaną aplikację musiał za każdym razem napisać
dużo kodu własnoręcznie (w przypadku PHP) lub zintegrować wiele technologii
(w przypadku języka Java) obejmujących język programowania, mapowanie
obiektowo relacyjne (Hibernate), szkielet MVC (Struts), narzędzie do budowania
aplikacji (Ant) oraz nauczyć się konfigurować wszystkie komponenty przy pomocy
języka XML. Można powiedzieć że Ruby on Rails powstał w odpowiedzi na
zaistniały stan rzeczy, z niechęci do powtarzania ciągle tych samych czynności
przy tworzeniu aplikacji oraz z nieuzasadnionego wydłużania się czasu uczenia
w przypadku popularnych technologii.
Zaistnienie RoR jest związane z duńskim programistą Davidem Heinemeierem
Hanssonem. Podczas swojej pracy nad aplikacją do zarządzania projektami
(BaseCamp) w firmie 37Signals stworzył rozbudowany framework ułatwiający
tworzenie aplikacji, zawierający wspomniane mapowanie obiektowo relacyjne
oraz wsparcie dla MVC. DHH opublikował swój framework na zasadach open
source w 2004 pod nazwą Ruby on Rails. Dzięki pracy społeczności Open Source
Ruby on Rails jest dziś dojrzałą technologią tworzenia stron internetowych,
posiadającą wsparcie w postaci wielu rozszerzeń, dokumentacji oraz książek.
Ponadto rozwiązania wprowadzone w Ruby on Rails okazały się na tyle ciekawe,
że zostały zaadoptowane we frameworkach takich jak CakePHP i Django (Python).
9
Rozdział 2
Koncepcje i techniki
programistyczne
Ruby on Rails został zbudowany w myśl prostych koncepcji mających na celu
zwiększenie wydajności programisty. Jego twórcy mają też określony pogląd na
sam proces tworzenia aplikacji, w związku z czym już od samego początku
zapewnia on szerokie wsparcie dla pewnych technik programistycznych, których
na co dzień używają korzystający z niego programiści. W niniejszym rozdziale
zostaną zaprezentowane zarówno jedne jak i drugie.
2.1. Koncepcje
Dwie kluczowe koncepcje miały swój wpływ na to, jak jest zbudowane i jak pracuje
się z Rails. Często na względzie mają je również twórcy dodatków, którzy starają
się, by ich komponenty były napisane w tym samym duchu.
Don't repeat yourself
Don't Repeat Yourself - zasada ta nakazuje umieszczanie każdej informacji
w systemie tylko raz w celu uniknięcia duplikacji [1], która zwiększa nakład pracy
niezbędny do dokonania zmian i stopień trudności ich wprowadzania oraz może
zmniejszyć przejrzystość kodu i prowadzić do niespójności (w sytuacji, gdy zmiana
zostanie odzwierciedlona tylko w jednym miejscu a pominięta w innym). W samym
środowisku jest ona doskonale zrealizowana, ponadto miejsce, w którym należy
zapisać taką informację, jest zwykle wyznaczone przez wzorzec MVC oraz
wygenerowany szkielet aplikacji. Jest to bardzo duży plus dla programistów,
którzy przychodzą do RoR z innych środowisk, gdzie nierzadko pojawia się spora
nadmiarowość.
Jednym z kontrprzykładów stosowania się do tej zasady jest NHibernate-
silnik do mapowania obiektowo relacyjnego dedykowany dla platformy .Net,
w którym zapisanie informacji o relacji jeden do wiele pomiędzy dwoma
mapowanymi tabelami, wiąże się z koniecznością edytowania czterech plików.
Tymczasem RoR korzystając z silnika ORM jakim jest ActiveRecord wymaga
w takiej sytuacji napisania jedynie dwóch linii kodu (zob. przykład w następnym
podrozdziale). Wszelkie informacje o mapowaniach zapisuje się korzystając cały
czas z tego samego języka programowania, bez konieczności zagłębiania się w
pliki konfiguracyjne (takie jak XML) i ich strukturę, co zdecydowanie zwiększa
czytelność informacji oraz zmniejsza nakład pracy jaki jest wymagany np. w
procesie refaktoryzacji.
Convention over configuration
Konwencje (w programowaniu) to zbiór zasad, przyjętych przez społeczność
korzystającą z danej technologi. Ich przestrzeganie nie jest konieczne do
poprawnego działania aplikacji, jednak zwykle przynosi szereg korzyści
(zwłaszcza podczas kolaboracji większej liczby osób), które przyczyniają się do
ich tak szerokiego stosowania, że możemy je nazwać właśnie tym określeniem.
Koncepcje i techniki
programistyczne
10
Niektóre konwencje wyłaniają się spontanicznie, inne zostają z góry narzucone
przez twórców.
Druga zasada jaka przyświeca twórcom Ruby on Rails to Convention Over
Configuration (konwencja ponad konfigurację) i objawia się ona we wszystkich
aspektach jego używania [1]. Oznacza ona, że wszelkie metody oraz notacje
używane przez nas przy pisaniu aplikacji mają bardzo sensowne wartości
domyślne, które mogą być pomijane zostawiając miejsce tylko tym istotnym. Jeśli
w swojej pracy stosujemy się do tych konwencji, struktura aplikacji oraz kod stają
się czytelniejsze oraz łatwiejsze do zrozumienia dla nowych osób pojawiających
się w projekcie. Nierzadko jedna linia w RoR oddaje to, co w innych frameworkach
musi być oddane, z użyciem dziesiątek linii kodu. Spójrzmy na poniższy przykład:
class User < ActiveRecord::Base
has_many :categories
end
class Category < ActiveRecord::Base
belongs_to :user
end
Dzięki dobrze dobranym wartościom domyślnym dla metod has_many oraz
belongs_to przekazanie jednego parametru podczas ich wywołania informuje
aplikację o (odpowiedzi zapisane w nawiasie dotyczą wywołania pierwszej z
metod):
• nazwie klasy, na którą mają być mapowane powiązane w bazie obiekty,
(Category)
• nazwie tabeli, z której mają być pobrane odpowiednie rekordy, (categories)
• kolumnie będącej kluczem głównym tabeli (kolumna id w tabeli users),
• kolumnie będącej kluczem obcym (kolumna user_id w tabeli categories),
• nazwie metody służącej do pobierania takich powiązanych obiektów (metoda
categories w klasie User).
Te dane mogą być jednak zmienione poprzez przekazanie parametrów takich jak:
class_name, foreign_key, primary_key.
CoC pozwala w prosty sposób wyrazić złożone fakty. Dobra realizacja tej
zasady nie odbiera programiście możliwości dokładnej konfiguracji ani nie
zmniejsza elastyczności używanych narzędzi czy funkcji, lecz jedynie minimalizuje
narzut pracy niezbędny na zdefiniowanie zachowań w sytuacjach typowych. W
nietypowych zaś, kiedy konwencja jest złamana, programista może po prostu
nadpisać parametry domyślne, które w przeciwnym wypadku pozostawiłby
niezmienione. Rails spopularyzował to podejście, które możemy już spotkać w
wielu innych frameworkach.
2.2. Techniki
O ile konwencje stosowane w RoR są raczej ogólnie przyjęte i niemal przez
wszystkich stosowane oraz uznawane, o tyle jeśli chodzi o zalecane techniki
Koncepcje i techniki
programistyczne
11
programowania ich realizacja nie zawsze przebiega bezproblemowo oraz płynnie.
Techniki te nie są nierozerwalnie związane tylko z tą jedną platformą czy językiem,
ani też w żaden sposób narzucane programistom Rails, jednak doświadczeni
deweloperzy często się nimi posługują oraz opisują w swoich pracach zyski
jakie przynoszą. Wybór ten podyktowany został zatem ich udokumentowaną
obecnością w świecie RoR oraz istniejącymi i szeroko opisywanymi narzędziami
wspierającymi ich realizację.
Test Driven Development
Test Driven Development jest techniką tworzenia oprogramowania, w myśl której
najpierw tworzy się testy opisujące sposób działania nowej funkcjonalności oraz
weryfikujące poprawność implementacji a dopiero następnie tworzy się kod
programu niezbędny by testy przeszły pomyślnie, by na końcu przejść do procesu
refaktoryzacji systemu.
Kiedy przychodzi nam do zaimplementowania nowej funkcjonalności, powinniśmy
się najpierw zastanowić, czy aktualna architektura aplikacji jest najlepszą
umożliwiającą nam uczynienie tego. Jeśli tak, możemy zacząć naszą iterację, jeśli
nie, musimy najpierw zmienić tą część architektury, która ma związek z naszym
zadaniem, by osiągnąć stan, w którym jego realizacja będzie najłatwiejsza
z możliwych. Dzięki posiadaniu już testów, które obejmują dotychczasowe
funkcjonalności, nie musimy się bać wprowadzanych zmian i możemy ich
dokonywać małymi krokami, sprawdzając jednocześnie jaki jest ich wpływ na
wynik testów. Można zatem w łatwy sposób dostrzec skutki wprowadzanych
usprawnień oraz inne miejsca w kodzie wymagające poprawek, by aplikacja wciąż
działała prawidłowo. Takie podejście sprawia, że architektura systemu podlega
ciągłemu doskonaleniu, czyniąc naszą pracę łatwiejszą w przyszłości.
Podejście TDD rządzi się dwiema żelaznymi zasadami [4]:
• napisz nowy kod tylko wtedy, kiedy nie przechodzi jeden z testów,
• unikaj powtórzeń.
Te dwie proste reguły mają swoje implikacje, zarówno w sposobie pracy grupy
programistów, jak i w budowie naszej aplikacji [5]:
• należy pisać własne testy, ponieważ nie możemy czekać kilkanaście razy
w ciągu dnia, aż napisze je ktoś inny,
• nasze środowisko musi nam dostarczać natychmiastowych informacji
nt. skutków naszych nawet najmniejszych zmian,
• aplikacja powinna składać się z wysoce spójnych i luźno powiązanych
komponentów, by ich testowanie było szybsze oraz łatwiejsze.
Ponadto wyznaczają one porządek wg. którego pracujemy [4]:
1. Dodaj test.
W TDD implementację każdej nowej funkcjonalności zaczyna się od napisania
testów. Oczywiście z początku ich sprawdzenie zakończy się porażką. Żeby
jednak napisać test programista musi poznać zarówno specyfikację jak
i wymagania funkcjonalności. Sprawdzenie, czy z początku test się nie powodzi
Koncepcje i techniki
programistyczne
12
jest niezbędne i stanowi warunek konieczny poprawności testu. Gwarantuje
nam, że nie będzie on zdawany zawsze, a tym samym stanie się bezużyteczny.
W tradycyjnym modelu testy powstają zwykle po napisaniu kodu. Podejście
odwrotne sprawia, że programista skupia się na wymaganiach jeszcze zanim
napisze kod, co wymaga od niego wyobrażenia sobie sposobu, w jaki będzie
chciał go używać w przyszłości.
2. Napisz kod.
Następny krok stanowi napisanie kodu, który przechodzi test. Jego jakość może
jednak wciąż pozostawiać wiele do życzenia. Jest to akceptowalne, gdyż ostatni
etap ma celu jego poprawę. Ważnym jest, by pisać tylko kod ukierunkowany
na zdanie napisanego wcześniej testu. Zasadniczo, nie powinno się w tym
momencie implementować funkcjonalności na przyszłość, a szczególnie takiej,
której nie pokrywa żaden z testów.
3. Uruchom testy i zobacz jak są poprawnie zdane.
Jeśli wszystkie testy są zdane, programista może być pewny, że jego kod spełnia
stawiane przed nim wymagania (zakładając, że napisał on rozsądne testy). Taka
sytuacja stanowi dobry punkt wyjścia do następnej, końcowej fazy.
4. Zrefaktoryzuj kod.
Jeśli istnieje taka potrzeba kod powinien zostać poddany refaktoryzacji,
szczególnie takiej, która usuwa powtórzenia. W tym etapie procesu programista
może obserwować czy wprowadzone przez niego zmiany nie zaburzą działania
systemu i są przeprowadzane właściwie. Nieustanna obecność testów podnosi
poziom zaufania do tworzonego przez siebie kodu, a także ułatwia rozeznanie
się, gdzie ewentualnie został popełniony błąd.
Jest to szczególny moment, którego nie należy pomijać, gdyż prowadzi on
do uniknięcia duplikacji (zgodnie z zasadą DRY) oraz łatwiejszego utrzymania
systemu w przyszłości, ze względu na lepszą architekturę.
Korzyści [6] [7] [8] [9]:
• Możliwe jest też zmniejszenie liczby błędów pojawiających się w aplikacji,
do tego stopnia, że gotowe do wdrożenia oprogramowanie zawierające nowe
funkcjonalności, dostępne jest każdego dnia, co może prowadzić do nowego
typu relacji z klientami oraz partnerami biznesowymi.
• Programiści, używający tego podejścia w zupełnie nowych projektach, dużo
rzadziej odczuwają konieczność korzystania z debuggera. W połączeniu
z używaniem systemu kontroli wersji może się okazać, że przywrócenie
ostatniej wersji przechodzącej testy jest bardziej produktywne niż korzystanie
z debuggera w celu znalezienia błędu.
• Pomimo faktu, że pisanie testów wymaga większej ilości kodu, całościowy koszt
implementacji jest zwykle niższy.
• Duża liczba testów przyczynia się do zmniejsza liczby błędów oraz ich
wychwytywania we wczesnych fazach pracy, zapobiegając tym samym
powstawaniu droższych problemów w późniejszym czasie, których poprawienie
wymagałoby debugowania aplikacji.
Koncepcje i techniki
programistyczne
13
• TDD może prowadzić do bardziej zmodularyzowanego, elastycznego
i  rozszerzalnego oprogramowania. Dzieje się tak, ponieważ wymaga się
od dewelopera myślenia o powstającej aplikacji przez pryzmat małych
jednostek, które mogą być napisane i przetestowane niezależnie, a następnie
zintegrowane ze sobą.
Wskazuje się na kilka faz oswajania z tą techniką [10]:
1. Programista zaczyna pisać testy jednostkowe dla swojego kodu, korzystając
z jednego z wielu istniejących na rynku narzędzi wspomagających ten proces
(zwykle framework z rodziny xUnit).
2. W miarę jak liczba testów wzrasta, programista zaczyna cieszyć się
wzrastającym poczuciem zaufania do wykonanej pracy.
3. Pojawia się odczucie, że pisanie testów przed napisaniem kodu, pomaga
skoncentrować się na napisaniu tylko tego, co jest niezbędne.
4. Programista zauważa, że gdy powraca do kodu, którego nie widział przez
pewien czas, testy służą mu jako dokumentacja jego działania.
5. Punkt zwrotny ma miejsce, gdy zauważa się, że pisanie testów w ten
sposób, pomaga odkryć API do własnego kodu, czyli wyobrazić sobie jak
chcielibyśmy z niego korzystać. Od tego momentu TDD staje się częścią
procesu projektowania.
6. Uświadomienie sobie, że TDD w większej mierze dotyczy definiowania
pożądanego zachowania (klasy, modułu, aplikacji), niż samego testowania.
7. Zachowanie to inaczej określenie sposobu interakcji komponentów systemu.
Podstawę zaawansowanego TDD stanowi zatem korzystanie z obiektów
imitujących pewne zachowania (mocking) w celu skrócenia i przyspieszenia
testów oraz zmniejszenia zależności między nimi.
Behaviour Driven Development
Skupienie się na zachowaniu systemu i jego poszczególnych elementów jest
cechą charakterystyczną naprawdę doświadczonych praktyków TDD. Jak się
jednak okazuje osiągnięcie punktu piątego z wymienionej w poprzednim rozdziale
listy faz okazuje się dla wielu programistów niezmiernie trudne [10]. Jednym
z  powodów dla którego ma się tak dziać jest nazewnictwo stosowane w TDD,
które niemal zawsze zawiera w sobie słowo test: przypadki testowe, zestawy
testów, metody testujące, testy akceptacyjne itd., co zdaje się sprawiać wrażenie,
że docelowym przeznaczeniem całej techniki jest właśnie testowanie aplikacji.
Tymczasem celem procesu jaki narzuca TDD jest tak naprawdę wyspecyfikowanie
co powinien robić tworzony kod nim zacznie się go pisać oraz dokumentowanie
działania aplikacji poprzez testy.
Początki Behaviour Driven Development są związane z powstaniem dwóch
frameworków (RSpec dla języka Ruby oraz JBehave dla Javy) opartych na
podobnych założeniach jak te z rodziny xUnit, jednak używających zupełnie innej
nomenklatury mającej zwrócić naszą uwagę, czy aby na pewno dana klasa, moduł,
bądź cała aplikacja powinna mieć taką funkcjonalność lub odpowiedzialność [11].
Słowa test oraz assert nie mają ponoć takiej mocy jak używane w BDD słowo
Koncepcje i techniki
programistyczne
14
should i nie pobudzają programisty do poddawania w wątpliwość, tego w jaki
sposób zaprojektował system oraz rozdzielił powinności.
Poniżej możemy zobaczyć przykładowy kod opisujący klasę Bowling korzystając
z frameworka RSpec. Specyfikuje on, że w przypadku bardzo kiepskiej gry
zawodnika, gdy dwudziestokrotnie nie udało mu się strącić żadnego kręgla, jego
końcowy wynik powinien wynosić 0.
describe Bowling do
it "should score 0 for gutter game" do
@bowling = Bowling.new
20.times { @bowling.hit(0) }
@bowling.score.should == 0
end
end
Ten sposób definiowania wymagań dużo lepiej nadaje się jednak do opisywania
klas czy poszczególnych modułów systemu i sprawdza się jako zamiennik
klasycznych frameworków testujących, niż jako sposób określenia zachowań
całego tworzonego systemu z punktu widzenia użytkownika końcowego. Jest on
zrozumiały dla programisty, ale nie jest to dokument, który można przedstawić
klientowi.
BDD kładzie duży nacisk na ścisłą współpracę z udziałowcami projektu, która
jest możliwa dzięki temu, że deweloperzy w procesie komunikacji posługują się
mieszanką języka naturalnego oraz języka specyficznego dla dziedziny w jakiej
rozwijany jest projekt. W szczególności ma to miejsce w scenariuszach czyli
dokumentach, gdzie wyraża się oczekiwane działanie systemu w konkretnych
sytuacjach przykładowych.
Poszczególnym krokom takiego scenariusza odpowiadają zaimplementowane
w języku programowania akcje wykonywane z użyciem scenariuszowego
frameworka testującego, narzędzia typowego dla BDD. Takim narzędziem dla
Rails jest Cucumber. Zapobiega to regresjom w trakcie tworzenia aplikacji oraz
automatyzuje proces weryfikacji zgodności działania systemu z oczekiwaniami
względem niego (scenariusze jako testy akceptacyjne).
Idea ścisłego przenikania języka dziedzinowego do języka aplikacji została
zaczerpnięta z podejścia do projektowania aplikacji znanego pod nazwą
Domain-driven design.
Przykładowy scenariusz, który może zostać przedstawiony interesariuszowi,
w celu weryfikacji poprawnego zrozumienia wymagań, a następnie po
zaimplementowaniu automatycznie wykonywany przez framework testujący:
Story: Logging in and out
Scenario: Logged in user can log out.
Given an activated user logged in as 'reggie'
When she goes to /logout
Then she should be redirected to the home page
When she follows that redirect!
Then she should see a notice message 'You have been logged out'
And she should not be logged in
And her session store should not have user_id
15
Rozdział 3
Podstawy języka Ruby
Pomimo prostej składni, język Ruby jest dość skomplikowany i opisanie
wszystkich jego elementów jest materiałem na grubą książkę. W rozdziale tym
zostaną opisane podstawy języka, które pozwolą lepiej zrozumieć przykłady
zawarte w  pracy, oraz elementy, które wpłynęły na tak dużą popularność tego
skryptowego języka.
3.1. Elementy składni
Język Ruby czerpie pełnymi garściami z innych języków skryptowych, takich jak
Perl czy Python, a to co go wyróżnia to łatwiejsza, bardziej przypominająca język
naturalny składnia.
Budowa kodu źródłowego
Programy w języku Ruby zapisywane są w plikach tekstowych (standardowo
zakończonych rozszerzeniem .rb), do edycji których wystarczy zwykły edytor.
Ruby w przeciwieństwie do np., Javy nie posiada ograniczeń dotyczących nazwy
i położenia plików w hierarchii katalogów, aczkolwiek przyjęło się, że jeśli w pliku
znajduje się definicja klasy np. MyClass to plik będzie nazwany w konwencji
podkreślnikowej: my_class [3].
Język Ruby jest całkowicie obiektowy, w tym sensie, że zawsze operujemy w nim
na obiektach i na metodach obiektów. Jednakże interpreter języka jest dość
elastyczny i nie narzuca nam pisania kodu w sposób obiektowy, dlatego program,
wypisujący prosty tekst, może przyjąć taką postać:
puts 'hello world'
Już tak prosty przykład pozwala nam poznać jedno z założeń języka - prostotę:
• Nie została zadeklarowana klasa, ani metoda w ramach której wykonywany
jest powyższy kod, jednakże interpreter języka dodał te elementy dynamicznie
przed uruchomieniem samego kodu.
• W powyższym przykładzie puts jest wywołaniem metody z modułu Kernel,
który jest automatycznie ładowany do każdego obiektu stworzonego w Ruby –
dzięki temu można wykonywać zadeklarowane w nim metody.
• Przekazanie parametru do metody może odbywać się bez używania nawiasów.
Wiele innych języków wymaga wpisania parametrów metod w nawiasy, w Ruby
są one opcjonalne.
• Brak separatorów instrukcji: standardowo znak nowej linii traktowany jest jako
separator instrukcji, ale można wpisać kilka instrukcji w jednej linii przy pomocy
znaku średnika.
Struktury kontrolne
Ruby posiada standardowe struktury kontrolne znane z innych języków, takich
jak C, czy Java. Podstawową różnicą między tymi strukturami, jest brak
Podstawy języka Ruby
16
nawiasów klamrowych oznaczających bloki kodu, zamiast których używa się słowa
kluczowego end jako oznaczenie końca bloku instrukcji.
If x > 0
puts x*2
elsif x < 0
puts x*4
else
puts x
end
Ciekawostką jest także to, że Ruby posiada specjalne słowo kluczowe pozwalające
sprawdzić czy warunek jest fałszywy:
unless record.save
flash[:notice] = 'Cannot save'
end
Dodatkowo instrukcje warunkowe mogą wystąpić po instrukcji której dotyczą:
errors.add_to_base(:wrong_order) if wrong_currencies_order?
Ciekawą właściwością języka jest też to, że każda struktura kontrolna zwraca
wartość (będąca wartością ostatniej wykonanej instrukcji), dzięki czemu można
jej użyć w wyrażeniu przypisania, np.:
begin_of_period = case symbol
when :THIS_DAY then Date.today
when :THIS_WEEK then Date.today.beginning_of_week
when :THIS_MONTH then Date.today.beginning_of_month
when :THIS_QUARTER then Date.today.beginning_of_quarter
when :THIS_YEAR then Date.today.beginning_of_year
end
W zmiennej begin_of_period znajdzie się obliczona data w zależności od
parametru symbol.
3.2. Programowanie obiektowe
Obiektowość Rubiego jest jedną z jego najczęściej wymienianych zalet, dlatego
poza zasadami budowy składni języka warto też poznać możliwości definiowania
obiektów, sposoby ich przechowywania oraz stosowania metaprogramowania.
Klasy, metody, obiekty i zmienne
Definiowanie zmiennych w Rubym polega na podaniu nazwy zmiennej
i przypisaniu do niej wartości. Zmienne nie mają typu[3], dlatego niepotrzebna
jest dodatkowa deklaracja znana z innych języków:
x = 5
Typ mają natomiast obiekty na które zmienne wskazują, x w powyższym
przykładzie ma typ Fixnum – jest to standardowa klasa do przechowywania liczb
całkowitych.
Podstawy języka Ruby
17
Programista może w Rubym definiować własne typy, służy do tego słowo kluczowe
class. Klasy mogą posiadać metody – je definiuje się przy pomocy słowa def.
Uproszczony przykład klasy Money ze zrealizowanego projektu:
class Money
def initialize(*args)
@hash = {}
end
def is_empty?
return @hash.keys.size == 0
end
end
Zmienne zaczynające swoją nazwę od znaku @ są traktowane przez Ruby jako
zmienne instancji obiektu, natomiast zmienne z dwoma znakami @ są traktowane
jako zmienne klasy (statyczne)[3].
Kontenery, iteratory
Ruby posiada dwie podstawowe klasy służące jako kontenery obiektów: Array
i Hash.
Posiadają one podobną funkcjonalność, ale różnią się sposobem indeksowania
elementów i literałami do tworzenia kolekcji:
• obiekty w Array przypisane są do liczb całkowitych, a samą tablicę tworzy się
przy pomocy kwadratowych nawiasów:
days = ['Mon', 'Tue', 'Wed']
puts days[0] # kod zwróci 'Mon'
• obiekty w Hash mogą być indeksowane przez dowolne inne obiekty, a kolekcję
tworzy się poprzez nawiasy klamrowe
values = {'category' => 'Expenses',
'value' => 5.3,
'currency' => 'PLN'}
puts values['value'] # kod zwróci 5.3
Kolekcje są dobrym przykładem do zaprezentowania iteratorów – jednej
z największych zalet tego języka, niezwykle wpływających na produktywność.
Iteratory są metodami zaimplementowanymi w kolekcjach, które pozwalają na
przejście przez wszystkie elementy danej kolekcji i wykonanie na nich operacji.
Podstawowym iteratorem jest each:
days.each |day| do
puts day
end
Powyższy kod będzie iterował po kolekcji dni, przypisywał aktualny element do
zmiennej day i wypisywał na ekranie za pomocą metody puts. Należy zauważyć,
że kod między do a end tworzy blok, który zostaje przekazany do metody each.
Podstawy języka Ruby
18
Ciekawym iteratorem jest też partition, który pozwala podzielić kolekcję na dwie
części, w zależności od tego czy blok kodu wykonany na danym elemencie zwrócił
prawdę czy fałsz. Poniższy kod dzieli tablicę liczb na liczby parzyste i nieparzyste:
even, odd = [1,2,5,66,78,99,142].partition do |number|
number % 2 == 0
end
W standardowych kolekcjach zdefiniowanych jest dużo więcej iteratorów różnego
przeznaczenia[3]. Można też tworzyć własne iteratory i metody które przyjmują
blok kodu, podobnie jak each.
Metaprogramowanie
Istotną cechą Rubiego, jest możliwość dynamicznego definiowania metod i klas
w czasie działania programu, w tym zmiana lub rozszerzenie działania klas
wbudowanych w język takich jak Array czy String, bez używania dziedziczenia.
Ruby pozwala nawet przechwycić próbę wywołania nieistniejącej metody
i wygenerować ją w razie potrzeby.
class Date
def next_quarter
self.at_end_of_quarter.next
end
end
Powyższy kod nie definiuje klasy Date, jak mogłoby się wydawać, ale przedefiniuje
standardowo istniejącą klasę dodając do niej nową metodę obliczającą początek
następnego kwartału.
Metaprogramowanie używane jest szczególnie w aplikacjach opartych na Rails,
np. przy definiowaniu relacji między klasami:
class User < ActiveRecord::Base
has_many :transfers
has_many :currencies, :dependent => :destroy
has_many :goals
end
Powyższy fragment implementacji klasy User, zawiera definicje relacji
użytkownika z innymi obiektami w modelu. Metoda has_many przyjmuje jako
parametr nazwę relacji a sama dynamicznie tworzy metody dostępowe do
tych kolekcji obiektów, takie jak: User.transfers, User.transfers.find,
User.transfers.empty? czy User.transfers.create.
19
Rozdział 4
Wielowarstwowa
architektura systemu
W rozdziale tym opisane zostaną ogólne aspekty architektury Ruby on Rails
a  także informacje o sposobie współpracy poszczególnych komponentów,
działaniu aplikacji, o tym jak rozłożone są pliki w standardowym projekcie, oraz
o tym jakich narzędzi i skryptów można używać podczas pracy nad projektem.
4.1. Czym jest Ruby on Rails
Ruby on Rails jest frameworkiem do szybkiego tworzenia aplikacji internetowych.
Innymi słowy Rails jest strukturą wspomagającą tworzenie, rozwój i testowanie
powstającej aplikacji, jest szkieletem zapewniającym podstawowe mechanizmy,
który może być wypełniany właściwą treścią programu [2].
Framework ten zyskał sobie wielu zwolenników dzięki zastosowaniu czytelnej
struktury kodu oraz ogromnym możliwościom połączonym z łatwością ich
użycia. Ponadto tworzenie aplikacji w Ruby On Rails nie wymaga używania
wyspecjalizowanych edytorów IDE (Integrated Development Environment), gdyż
wszystkie czynności można wykonać w konsoli systemowej a pliki edytować
w  najprostszym edytorze tekstu. W istocie powstały takie zintegrowane
środowiska deweloperskie dedykowane pracy w Ruby on Rails, jednak ich
działanie polega głównie na kolorowaniu składni oraz wykonywaniu skryptów
i generatorów dostępnych w samym środowisku, dlatego też wszystkie przykłady
umieszczone w tej pracy ograniczają się wyłącznie do interakcji ze zwykłą konsolą
tekstową.
Pracę z Ruby on Rails zaczyna się od wygenerowania nowego projektu przy
pomocy polecenia rails, podając jako parametr nazwę tworzonej aplikacji [2]:
rails manage_my_money
create
create app/controllers
create app/helpers
create app/models
create app/views/layouts
create config/environments
create config/initializers
create config/locales
(...)
Instrukcja ta tworzy szereg plików i katalogów, z których każdy ma określone
zastosowanie:
• app – Znajduje się tu główny kod aplikacji.
• app/controllers – Katalog przeznaczony jest na kod warstwy kontrolera:
klasy poszczególnych kontrolerów, oraz klasa głównego kontrolera
(ApplicationController) z którego dziedziczą pozostałe.
Wielowarstwowa
architektura systemu
20
• app/models – Znajdują się tutaj klasy składające się na warstwę modelu
– w większości klasy uczestniczące w mapowaniu obiektowo relacyjnym.
• app/views – Miejsce składowania szablonów dla warstwy widoku.
• app/views/layouts – W tym katalogu znajdują się specjalne szablony,
zawierające elementy dostępne na wielu ekranach aplikacji, takie jak np. stopka
czy menu.
• app/helpers – Umieszczone tutaj zostają tzw. helpery, czyli moduły
wspomagające działanie szablonów widoku. Są one przydatne jeśli np.
dane otrzymane od warstwy kontrolera trzeba wstępnie przetworzyć przed
wyświetleniem. Poniższy helper definiuje metodę, która zamienia nazwę
kodową pewnej opcji w systemie na jej nazwę zrozumiałą dla użytkownika:
module GoalsHelper
def description_for_completion_condition(code)
case code
when :at_least then 'Co najmniej'
when :at_most then 'Co najwyzej'
end
end
end
Zdefiniowana w ten sposób metoda description_for_completion_condition
może być następnie użyta w implementacji warstwy widoku.
• config – Katalog przechowujący pliki konfiguracyjne, najważniejsze z nich to
database.yml definiujący połączenia z bazą danych, routes.rb zawierający
ścieżki routingu oraz enviroment.rb – zawierający różnorodne ustawienia dla
całego frameworku.
• db – Zawiera skrypt schema.rb definiujący aktualny schemat bazy danych na
którym działa aplikacja, oraz pliki migracji generujące go w sposób przyrostowy.
• doc – Przeznaczony na dokumentajcę projektu, która zwykle jest generowana
na podstawie komentarzy w kodzie.
• lib – W tym miejscu umieszcza się dodatkowe moduły i biblioteki, które
nie należą do modelu, widoku ani kontrolera. Biblioteki takie ładowane są
automatycznie na starcie aplikacji.
• public – Zawiera tzw. pliki statyczne aplikacji. Są to elementy, które nie
wymagają przetwarzania po stronie serwera aplikacji, takie jak obrazki czy
kaskadowe arkusze stylów oraz pliki JavaScript.
• script – Zawiera skrypty pomocnicze, np. generatory kodu czy skrypty
umożliwiające uruchomienie serwera.
• test – W tym katalogu znajdują się pliki testów automatycznych.
• vendor/plugins – Zawiera zewnętrzne moduły niezbędne do działania aplikacji
czyli tzw. pluginy.
• vendor/rails – W tym folderze można umieścić wszystkie biblioteki składające
się na framework, przydatne jeśli w systemie zainstalowana jest inna wersja niż
wymagana przez projekt.
Wielowarstwowa
architektura systemu
21
4.2. Przypływ sterowania i model MVC
Istotną cechą frameworku Ruby on Rails jest oparcie go na solidnej
podstawie jaką jest wzorzec projektowy MVC (Model View Conroller). Wzorzec
projektowy to inaczej zbiór zasad poprawnego tworzenia i projektowania
oprogramowania. Rozumienie i stosowanie wzorców pozwala nie tylko tworzyć
lepsze oprogramowanie, ale też przyśpiesza komunikację między programistami,
którzy nie muszą tłumaczyć sobie nawzajem zawiłych czasem pomysłów
architektonicznych – porozumiewają się używając ich nazw.
Istnieje wiele takich wzorców projektowych i wiele z nich zostało użytych w Rails,
jednakże MVC jest nadrzędny wobec każdego innego – ustala wysokopoziomową
strukturę projektu, każdy element systemu podlega MVC.
Model-Widok-Kontroler został zaproponowany w 1979 roku przez Trygve
Reenskaug[1] – wymyślił on nowe podejście do budowania aplikacji z graficznym
interfejsem użytkownika, polegające na wyodrębnieniu z projektu części
odpowiedzialnych za:
• dostęp i operacje na danych (model),
• prezentację interfejsu (widok),
• odbieranie poleceń użytkownika i zarządzanie przetwarzaniem (kontroler).
Model ten był przez lata z powodzeniem używany przy projektowaniu aplikacji
desktopowych i z tych właśnie doświadczeń czerpali też twórcy frameworku
Ruby on Rails. Zasadę działania MVC i jednocześnie Rails, najłatwiej zobrazować
na ogólnym schemacie prezentującym pojedyncze zapytanie HTTP do serwera
aplikacji: [1]
Rysunek 4.1. Wykres przepływu w modelu MVC.
1. Na początku użytkownik, poprzez przeglądarkę internetową, wysyła do serwera
aplikacji formularz HTML, który wraz z innymi parametrami jest przesyłany
protokołem HTTP do serwera aplikacji. Następnie serwer dobiera odpowiedni
kontroler i przekazuje do niego żądanie.
Wielowarstwowa
architektura systemu
22
2. Kontroler kontaktuje się z modelem, celem odczytania lub zapisania rekordów
w  bazie danych, oprócz tego model może wykonać specyficzną logikę
biznesową związaną z danymi lub przeprowadzić ich walidację.
3. Po wykonaniu operacji na modelu, kontroler przekazuje sterowanie do
odpowiedniego szablonu widoku dostarczając do niego dane otrzymane
z warstwy modelu.
4. Warstwa widoku generuje z szablonu i dostarczonych danych kod HTML, który
jest następnie wysyłany do przeglądarki. Przeglądarka wyświetla stronę WWW
i czeka na dalsze polecenia użytkownika.
Tak, w ogólnej perspektywie przedstawia się działanie serwera w przypadku
jednego żądania. Cykl życia aplikacji polega natomiast na nieustannym
odpowiadaniu na takie żądania od wielu użytkowników.
4.3. Narzędzia
Ruby on Rails to nie tylko zbiór komponentów i bibliotek współpracujących ze sobą
podczas obsługi żądania. Na framework składa się też szereg narzędzi i skryptów,
które w różnym stopniu wspomagają pracę programisty, należą do nich m.in. [2]:
• ./script/server – Jeden z ważniejszych skryptów, powoduje uruchomienie
aplikacji na domyślnym serwerze aplikacji, którym w tym przypadku jest
WEBrick:
./script/server
=> Booting WEBrick
=> Rails 2.3.2 application starting on http://0.0.0.0:3000
=> Call with -d to detach
=> Ctrl-C to shutdown server
[2009-05-22 23:14:41] INFO WEBrick 1.3.1
[2009-05-22 23:14:41] INFO ruby 1.8.7 (2008-08-11) [i486-linux]
[2009-05-22 23:14:46] INFO WEBrick::HTTPServer#start: port=3000
Powyższe wywołanie umożliwia interakcję z uruchomioną aplikacją na lokalnym
adresie i porcie 3000.
• ./script/console – Skrypt otwiera tzw. konsolę deweloperską – narzędzie
umożliwiające interaktywną pracę z frameworkiem, bez faktycznego
uruchamiania serwera aplikacji. Daje dostęp przede wszystkim do całego
modelu i umożliwia np. znalezienie pierwszego użytkownika z bazy danych
i szybką zmianę jego hasła, wynik podawany jest natychmiast po zatwierdzeniu
polecenia:
./script/console
Loading development environment (Rails 2.3.2)
>> u = User.find :first
=> #<User id: 2, login: "robert", name: "",
email: "robert@example.org", created_at: "2009-03-27 22:52:14",
updated_at: "2009-03-31 11:59:19">
>> u.password = "123456"
=> "123456"
>> u.save!
Wielowarstwowa
architektura systemu
23
• ./script/generate – Skrypt pozwalający generować elementy różnego
przeznaczenia: modele, kontrolery, migracje bazy danych. Zajmuje się on
stworzeniem odpowiednich plików i wypełnieniem ich wymaganą treścią,
pozwalając programiście skupić się na najważniejszym - implementowaniu
logiki biznesowej. Ponadto można rozbudowywać funkcjonalnośc tego
narzędzia tworząc własne szablony dla generatora lub instalując je w postaci
pluginów.
• rake – Jest to program wspomagający budowanie aplikacji, wzorowany
na znanym narzędziu make. Umożliwia proste uruchamianie wcześniej
zdefiniowanych zadań. Rails dostarcza spory zestaw takich zadań,
pozwalających m.in. na utworzenie bazy danych, zainstalowanie komponentu
zewnętrznego czy uruchomienie testów aplikacji. Istnieje możliwość
definiowania nowych, dodatkowych zadań dla rake.
4.4. Komponenty
Rails nie jest monolityczną strukturą, w rzeczywistości poza rdzeniem
frameworku, tworzą go następujące moduły [2]:
• Active Support – zestaw bibliotek i dodatków do języka Ruby, ułatwiający
operacje na m.in. ciągach znaków, datach i liczbach,
• Active Record – biblioteka do obsługi relacyjnej bazy danych,
• Action Pack – obsługa warstwy kontrolera, widoku i routingu,
• Action Mailer – umożliwia wysyłanie i odbieranie poczty elektronicznej,
• Active Resource – biblioteka do komunikacji z zewnętrznymi systemami,
opartymi na interfejsie REST (Representational state transfer).
Poza standardowymi modułami, programista ma możliwość rozszerzania
funkcjonalności frameworku o zewnętrzne komponenty, czyli tzw pluginy. Łatwość
tworzenia i używania pluginów sprawiła, że powstały ich setki, dzięki czemu Rails
może spełniać najróżniejsze wymagania projektowe.
Przykładowo, w projekcie Manage My Money, używanych jest 10 zewnętrznych
komponentów, co przystępnie obrazuje poniższy diagram:
Wielowarstwowa
architektura systemu
24
Rysunek 4.2. Używane w projekcie rozszerzenia
Pokrótce funkcjonalność tych rozszerzeń przedstawia się następująco:
• SSL Requirement - Wymusza pracę aplikacji w bezpiecznym trybie SSL (Secure
Socket Layer),
• Exception Notification - Umożliwia automatyczne przesyłania wiadomości email
do zdefiniowanych odbiorców z treścią wyjątku jaki spowodował użytkownik
w czasie pracy z aplikacją,
• Open Flash Chart - Umożliwia generowanie wykresów w technologii Flash,
• Autocomplete - Służy do implementacji funkcjonalności dopełniania
wprowadzanych danych,
• Validates Equality - Napisany w ramach projektu plugin, który umożliwia łatwe
sprawdzanie, czy pewne atrybuty obiektu są takie same dla niego jak i dla
kolekcji innych elementów, które zawiera,
• FasterCVS - Parser plików CSV (Comma Separated Values),
• Thinking Sphinx – Dostarcza API do komunikacji z silnikiem wyszukiwania
pełnotekstowego Sphinx. Umożliwia jego konfigurację na poziomie klas modelu
zamiast w zewnętrznych plikach. Wspiera tym samym realizację koncpecji DRY
oraz pozwala używać tego samego języka programowania do wyrażenia różnych
faktów.
• Nokogiri - Biblioteka do parsowania XML/HTML,
• Restful authentication - umożliwia generowanie klas odpowiedzialnych za
zarządzanie użytkownikami,
• Awesome nested set - Umożliwia proste dokonywanie operacji na elementach
składających się na struktury drzewiaste. Dostarcza bardziej optymalnych
zapytań do wyszukiwania potomków oraz przodków w drzewach niż
standardowe rozwiązania.
25
Rozdział 5
Budowa aplikacji
Ruby on Rails jest frameworkiem warstwowym, a jego główna idea
architektoniczna oparta jest na wzorcu MVC. Znakomita większość kodu projektu
trafia więc do jednej z trzech warstw: modelu, obsługującego dostęp do bazy
danych; widoku, generującego interfejs użytkownika oraz kontrolera, który
wszystko nadzoruje.
Rozdział ten opisuje budowę i działanie warstw frameworku posiłkując się przy
tym przykładami kodu z projektu Manage My Money.
5.1. Kontroler
Warstwa kontrolera jest odpowiedzialna za odbieranie żądań od użytkownika,
delegowanie zadań związanych z logiką biznesową do warstwy modelu
i  dobieranie odpowiedniego widoku celem wysłania w odpowiedzi na dane
żądanie.
Tworzenie kontrolera
Każdy kontroler w Rails jest klasą, która dziedziczy z klasy kontrolera aplikacji
i jest umieszczona w pliku w folderze /app/controllers. Plik ten można utworzyć
samodzielnie, jednak znacznie łatwiej jest skorzystać z dostępnych skryptów do
generowania kodu.
app> script/generate controller users
exists app/controllers/
exists app/helpers/
create app/views/users
exists test/functional/
exists test/unit/helpers/
create app/controllers/users_controller.rb
create test/functional/users_controller_test.rb
create app/helpers/users_helper.rb
create test/unit/helpers/users_helper_test.rb
Powyższe polecenie, oprócz klasy kontrolera, utworzy też m.in. folder, w którym
umieszczone zostaną szablony widoku, a także klasę do jego testowania.
Utworzony w ten sposób kontroler przyjmie taką postać:
class UsersController < ApplicationController
end
Zgodnie z konwencją Rails nazwa kontrolera została rozszerzona o sufiks
Controller a sam kontroler dziedziczy z klasy ApplicationController. Poza
tym nie on ma żadnej funkcjonalności, gdyż nie zostały w nim definiowane żadne
metody.
Budowa aplikacji
26
Budowa kontrolera
Publiczne metody instancji kontrolera w Rails nazywane są akcjami. Akcje te są
dostępne z zewnątrz, np. z poziomu formularza na stronie widoku.
Programista ma dużą dowolność w tworzeniu akcji kontrolera, jednak jeśli dany
kontroler jest odpowiedzialny za operacje CRUD (create, read, update, delete)
na modelu (a jest tak w większości przypadków) to powinien zastosować zestaw
metod zgodny z konwencją [1]:
• new, create - para metod służąca do tworzenia nowych krotek w bazie danych,
metoda new jest odpowiedzialna za wysłanie do przeglądarki użytkownika
formularza pozwalającego na określenie atrybutów nowego obiektu, podczas
gdy metoda create interpretuje te atrybuty i zapisuje nową krotkę w bazie,
• edit, update - para metod, o analogicznym działaniu jak new, create, ale
pozwalająca edytować istniejące obiekty,
• destroy – metoda mająca za zadanie usuwanie obiektów z bazy danych,
• index – wyświetlanie listy wszystkich elementów tabeli,
• show – wyświetlenie konkretnego elementu, na podstawie jego identyfikatora.
Niezależnie od tego, jaka konwencja zostanie przyjęta, do akcji zawsze
przekazywane są obiekty pozwalające uzależnić przetwarzanie od czynników
zewnętrznych, należą do nich [2]:
• params - hash zawierający parametry żądania, np. w przypadku zapytania POST
wysłanego ze strony HTML, params będzie zawierał pary postaci: nazwa_pola
=> wartość_pola_formularza,
• request - zawiera szczegółowe informacje o żądaniu, takie jak adres pod który
skierowano zapytanie, czy nazwa przeglądarki użytkownika,
• session - hash przechowujący między żądaniami dowolne zapisane w nim
dane, tzw. sesja użytkownika.
Generowanie odpowiedzi
Akcja kontrolera, poza odbieraniem żądania, wywołaniem logiki biznesowej, ma
też za zadanie dostarczenie odpowiedniego widoku do przeglądarki użytkownika.
Standardowym zachowaniem, jeśli w akcji nie zostało zdefiniowane inaczej, jest
odszukanie w odpowiednim folderze szablonu o takiej samej nazwie jak akcja.
Dla przykładu:
class CurrenciesController < ApplicationController
def index
@currencies = Currency.for_user(@current_user).find(:all,
:order => 'name')
end
end
Budowa aplikacji
27
Wywołaniu akcji index towarzyszy wygenerowanie kodu HTML na podstawie
szablonu widoku zawartego w pliku app/views/currencies/index.html.erb.
Standardowe zachowanie można zmieniać na wiele sposobów, dla przykładu
weźmy metodę create, z kontrolera zarządzającego walutami:
def create
@currency = Currency.new(params[:currency])
@currency.user = @current_user
if @currency.save
flash[:notice] = 'Created new unit.'
redirect_to :action => :index
else
render :action => 'new'
end
end
Akcja create jest wywoływana przy wysyłaniu formularza HTML podczas
tworzenia nowej waluty. Metoda ta stara się utworzyć nową walutę na podstawie
otrzymanych parametrów formularza, oraz zapisać ją przy pomocy metody save.
Powodzenie w zapisywaniu skutkuje wywołaniem metody redirect_to, która to
zwraca do klienta żądanie przekierowania na podany adres (w tym przypadku
będzie to adres odpowiadający akcji wyświetlającej wszystkie waluty).
Niepowodzenie natomiast, powoduje użycie metody render i wyświetlenie
szablonu new używanego do prezentacji formularza. Takie zachowanie umożliwia
użytkownikowi poprawienie wprowadzonych danych. W tym przypadku nie
następuje przekierowanie.
Filtry
Filtry są mechanizmem działającym na poziomie kontrolera, pozwalającym na
wykonywanie dodatkowych czynności w ramach danej akcji, lub grupy akcji.
Używanie filtrów pozwala oczyścić plik z kodu, który normalnie należało by
powtarzać w prawie każdej akcji.
Dostępne są trzy rodzaje filtrów [1]:
• before_filter – umożliwia wykonanie dodatkowych metod, jeszcze przed
wykonaniem samej akcji.
Umieszczenie następującej linijki w kodzie klasy kontrolera powoduje
wykonanie metody login_required przed każdą jego akcją.
before_filter :login_required
Taki zapis daje możliwość zablokowania dostępu do akcji dla niezalogowanych
użytkowników, jeszcze przed wykonaniem samej metody kontrolera. Działanie
filtrów można ograniczać do zadanej grupy akcji:
before_filter :check_perm_edit, :only => [:edit, :destroy]
lub podawać wyjątki nie podlegające działaniu filtrów:
before_filter :login_required, :except => :login
Budowa aplikacji
28
• after_filter – działa tak samo jak before_filter, z tą różnicą, że wywołuje
metody po wykonaniu bieżącej akcji, lecz jeszcze przed wysłaniem jakiejkolwiek
odpowiedzi do klienta, pozwala to np. na dodanie kompresji danych.
• around_filter – łączy w sobie cechy obu poprzednich filtrów – umożliwia
zdefiniowanie w jednej metodzie kodu do wykonania przed i po akcji, np.
dodanie logowania:
around_filter :logger
def logger
logger.log "Staring processing something at #{Time.now}"
yield
logger.log "Finished processing something at #{Time.now}"
end
Kluczowe jest tutaj użycie słowa yield, które powoduje wykonanie samej akcji.
Routing
W poprzednich podrozdziałach zostało zaznaczone, że publiczne metody
kontrolera są dostępne jako akcje w aplikacji. Interfejsem pozwalającym
na wywoływanie tych akcji jest odpowiednio zdefiniowany adres URL.
Przypisywaniem adresów URL do akcji zajmuje się w Rails moduł routingu.
Ustawienia związane z routingiem znajdują się w pliku app/config/routes.rb,
jego standardowa zawartość:
ActionController::Routing::Routes.draw do |map|
map.connect ':controller/:action/:id'
end
powoduje, że adres postaci localhost/currencies/edit/1 potraktowany
zostanie jako wywołanie metody edit z kontrolera currencies i przekazanie
parametru 'id' o wartości 1.
Konfigurację routingu można rozszerzać o własne reguły, np. zdefiniować
specjalną składnię adresu dla części administracyjnej serwisu
map.connect 'admin/:action/:id'
:controler => :user,
:admin_action => true
w ten sposób odwołania postaci admin/edit/2 będą przekazywane do kontrolera
User, z dodatkowym parametrem admin_action.
5.2. Model
Najważniejszą warstwą aplikacji Rails jest warstwa modelu. Odpowiada ona za
obsługę modelu biznesowego, czyli przechowywanie i walidację danych, ponadto
cała logika biznesowa powinna być umieszczona w tej warstwie.
Active record
Active Record jest modułem zajmującym się mapowaniem obiektowo relacyjnym.
Jego użycie sprawia, że korzystanie z bazy danych nie wiąże się z używaniem
Budowa aplikacji
29
języka SQL – programista operuje na obiektach reprezentujących rekordy w bazie
a framework sam generuje odpowiednie zapytania. AR upraszcza sposób pracy
z bazą danych – tabele z bazy stają się klasami, rekordy stają się obiektami,
a kolumny – atrybutami tych obiektów [2].
Przykładowo, tabela currencies, zdefiniowana w SQL następująco:
CREATE TABLE currencies (
id integer NOT NULL,
symbol character varying(255) NOT NULL,
long_symbol character varying(255) NOT NULL,
name character varying(255) NOT NULL,
long_name character varying(255) NOT NULL,
user_id integer
);
zostaje zmapowana na klasę Currency.
class Currency < ActiveRecord::Base
end
Obiekty klasy zaimplementowanej w ten sposób, będą posiadały metody służące
do odczytywania i zapisywania wartości krotek w bazie, m.in. long_symbol(),
long_symbol=(a_long_symbol), name(), name=(a_name), czy też user_id()
oraz user_id=(a_user_id).
Konfiguracja połączenia
Używając AR należy upewnić się, że konfiguracja połączenia z  bazą danych
umieszczona w pliku config/database.yml jest poprawna. Znajdują się tam
osobne ustawienia dla trzech domyślnych środowisk: testowego, produkcyjnego
i developerskiego. Każde z nich wymaga podania następujących parametrów [1]:
• adapter: określa rodzaj używanej bazy, podanie tego parametru jest konieczne,
gdyż producenci systemów bazodanowych stosują w swoich bazach różne
dialekty języka SQL. Rails wspiera wiele takich dialektów (między innymi
MySQL, PostrgreSQL, MSSQL, DB2, Oracle) i dzięki temu znosi z programisty
obowiązek implementowania tych różnic samemu.
• database: nazwa bazy danych,
• host: adres bazy danych,
• username: nazwa użytkownika bazy danych,
• password: hasło dla podanego użytkownika.
Przykładowa konfiguracja środowiska testowego może wyglądać następująco:
test:
adapter: postgresql
database: money_test
host: localhost
username: postgres
password: postgres
Budowa aplikacji
30
Migracje
Migracje są mechanizmem pozwalającym zarządzać strukturą bazy danych
podobnie jak systemy kontroli wersji zarządzają kodem aplikacji. Dodatkowo
stanowią pewien poziom abstrakcji nad instrukcjami DDL (Data Definition
Language) systemów baz danych. Dzięki temu, że kod migracji jest zapisany
w języku Ruby, ewentualna zmiana systemu bazodanowego nie stanowi dużego
problemu.
Migracje umożliwiają [1]:
• tworzenie tabel, zdefiniowanie kolumn, ich typów i atrybutów:
create_table :goals do |t|
t.string :description
t.boolean :include_subcategories
t.integer :period_type_int
t.integer :goal_type_int
(...)
end
• usuwanie tabel:
drop_table :goals
• zmianę kolumn w już istniejących tabelach:
add_column :users, :transaction_amount, :integer, :null => true
remove_column :users, :include_transactions_from_subcategories
change_table(:goals) do |t|
t.date :period_start
t.date :period_end
end
• zakładanie indeksów:
add_index :system_categories, :id, :unique => true
Migracje przechowywane są w plikach, z których każdy odpowiada za pewną
zmianę struktury bazy danych. Ich wykonanie wiąże się z wprowadzeniem
tych zmian oraz aktualizacją numeru wersji struktury bazy, co pozwala
przy uruchamianiu następnych migracji wykonać tylko te, które nie zostały
zaaplikowane wcześniej. Dzięki swej specyficznej budowie migracje pozwalają
również na wycofanie wcześniejszych zmian.
Relacje
Poza odwzorowywaniem pól tabeli na pola obiektów ActiveRecord potrafi również
mapować relacje między tabelami. Tworzy się je umieszczając jedno z dostępnych
makr: has_one, has_many, belongs_to oraz has_and_belongs_to_many
w klasach których relacja dotyczy. AR obsługuje następujące typy relacji:
• Relacja jeden do wielu - ta relacja zakłada, że jedna z tabel ma kolumnę, która
wskazuje na klucz główny drugiej tabeli,
Budowa aplikacji
31
create_table :categories do |t|
t.integer :user_id
(...)
end
create_table :users do |t|
(...)
end
Tak zdefiniowanym tabelom i kluczom odpowiadają następujące definicje
w klasach:
class Category < ActiveRecord::Base
belongs_to :user
end
class User < ActiveRecord::Base
has_many :users
end
Dzięki tym zapisom możliwe są m.in. następujące wywołania:
@user.categories – zwraca listę wszystkich obiektów użytkownika,
@category.user – zwraca użytkownika danej kategorii.
• Relacja wiele do wielu – w tym przypadku klucze obce nie są definiowane
w samych tabelach których relacja dotyczy, ale w dodatkowej zawierającej
tylko pary kluczy. Tabela reprezentująca relację Category i SystemCategory ma
postać:
create_table :categories_system_categories, :id => false do |t|
t.integer :category_id, :null => false
t.integer :system_category_id, :null => false
end
Nie jest wymagane by tabela relacyjna categories_system_categories była
mapowana obiektowo, należy natomiast dodać zapisy
has_and_belongs_to_many :categories
oraz
has_and_belongs_to_many :system_categories
odpowiednio do klas SystemCategory i Category.
• Relacja jeden do jednego – jest to szczególny przypadek relacji jeden do wielu,
gdyż klucze obce w tabelach zdefiniowane są identycznie, jedyna różnica leży
w zapisie has_many, który zmienia się na has_one.
Operacje na rekordach
AR realizuje wszystkie podstawowe operacje na rekordach, należą do nich [2]:
Budowa aplikacji
32
• Tworzenie rekordów. Najłatwiej nowy rekord tworzy się przy pomocy
konstruktora klasy zmapowanej na daną tabelę i przypisaniu wartości
odpowiednim polom, a następnie wykonanie metody save na tak
zainicjalizowanym obiekcie. Dokładnie tak zaimplementowane to zostało
w następującej metodzie tworzącej dane do testów:
def create_share_report(user)
new_share_report = ShareReport.new
new_share_report.user = user
new_share_report.name = "Testowy raport"
new_share_report.save!
(...)
end
Tym sposobem w bazie danych zostanie zapisany nowy wiersz w tabeli raportów.
Inną możliwością utworzenia rekordu jest przekazanie do konstruktora hasha
z nazwami i wartościami pól. Z tego rozwiązania korzysta się zazwyczaj w kodzie
kontrolerów, by sprawnie utworzyć nowy obiekt z parametrów formularza:
def create
@goal = Goal.new(params[:goal])
@goal.save!
end
• Kasowanie rekordów. Istnieje możliwość bezpośredniego skasowania wiersza
tabeli, używając metody delete(id), gdzie id jest wartością klucza głównego.
Natomiast do kasowania rekordów, które zostały zainstancjonowane w postaci
obiektów służy metoda destroy:
@goal = @current_user.goals.find(params[:id])
@goal.destroy
• Aktualizacja rekordów. Zmianę wartości rekordów najłatwiej wprowadzić
zmieniając wartości pól obiektu reprezentującego dany wiersz i, podobnie jak
przy tworzeniu obiektu, wykonać metodę save:
@goal = @current_user.goals.find(params[:id])
@goal.user = another_user
@goal.save
Można również użyć metody update_attributes!, która przyjmuje hash
wartości zmienionych pól
@goal.update_attributes!({:name => 'Changed name', :user => nil})
• Odczyt rekordów. Odczyt i wyszukiwanie danych jest jedną z najczęstszych
czynności podczas pracy z bazą danych, dlatego też AR definiuje całą gamę
możliwości dotarcia do danych.
Podstawowym narzędziem wyszukiwania danych jest metoda klasowa find,
wywołanie jej z jednym parametrem spowoduje wyszukanie rekordu z danej
tabeli o danym kluczu głównym:
User.find 1
Budowa aplikacji
33
Do metody find można przekazać opcje zawężające wynikowy zbiór danych:
w postaci parametryzowanych ciągu znaków:
Goal.find :conditions => ['period_start = ? AND period_end = ?,
new_goal.period_start, new_goal.period_end]
lub w postaci hasha:
Goal.find :conditions => {:period_start => Date.today,
:period_end => Date.tomorrow }
Dostępne są również metody wyszukujące specyficzne dla danego modelu, np.
model Report, który posiada pola name oraz depth, można przeszukiwać przy
pomocy wywołania:
Report.find_by_name_and_depth('Report1', 5)
Czasem istnieje potrzeba odrzucenia zalet standardowych metod
i  bezpośredniego wykonania na bazie danych zapytania SQL, służy do tego
metoda find_by_sql
User.find_by_sql "SELECT * FROM users"
Walidacje
Active Record wprowadza mechanizm utrzymania spójności danych biznesowych,
w znacznym stopniu eliminujący potrzebę używania specyficznych dla systemu
baz danych wyzwalaczy, czy tez procedur składowanych. Walidacje są metodami
wykonywanymi na poziomie klasy modelu, mającymi za zadanie sprawdzanie
przed zapisem rekordu do bazy czy jest on zgodny z wytycznymi, określonymi
w parametrach walidacji [2].
Walidacje operują na polach obiektów, lub grupach pól. Do podstawowych
walidacji należą:
• validates_presence_of :period_start, :period_end
Zapewnia, że podane pola nie są puste,
• validates_numericality_of :max_categories_values_count,
:only_integer => true, :greater_than_or_equal_to => 1
Zapewnia, że pole podane jako pierwszy parametr będzie liczbą, dodatkowe
parametry zawężają zbiór dopuszczalnych wartości do liczb całkowitych
większych lub równych 1,
• validates_length_of :login, :within => 3..40
Zapewnia, że długość ciągu znaków zawiera się między 3 a 40,
• validates_format_of :email,
:with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i
Walidacja pola względem wyrażenia regularnego.
Budowa aplikacji
34
Gdy podstawowe walidacje nie są wystarczające, można zadeklarować własną
metodę walidującą:
class MultipleCategoryReport < Report
validate :has_at_least_one_category?
def has_at_least_one_category?
if category_report_options.empty?
errors.add_to_base(:should_have_at_least_one_category)
end
end
end
W tym przypadku metoda validate przyjmuje jako parametr nazwę metody,
której zadaniem jest zwalidowanie danego modelu pod względem posiadania co
najmniej jednego obiektu category w relacji jeden do wielu.
5.3. Widok
Ostatnim, lecz nie mniej ważnym elementem każdej aplikacji Rails, jest warstwa
widoku – jej zadaniem jest generowanie na żądanie kontrolera (oraz przy pomocy
zmiennych przez niego przekazanych) kodu w formacie rozumianym przez klienta
aplikacji. W zależności od potrzeb będzie to HTML, Java Script lub nawet XML.
Szablony widoku
Kod widoku zlokalizowany jest w plikach zwanych szablonami, w folderze app/
views. Nazwa pliku szablonu odpowiada zazwyczaj nazwie odpowiadającej akcji
w kontrolerze, natomiast rozszerzenie uzależnione jest od formatu wynikowego
oraz wybranego mechanizmu przetwarzania [2]. Domyślnym mechanizmem jest
ERB (Embeded Ruby – zagnieżdżony Ruby), dlatego plik szablonu generującego
odpowiedź do przeglądarki użytkownika, na zapytanie o  listę obiektów typu
Category, będzie miał nazwę categories/index.html.erb
Szablony składają się głównie z kodu w formacie wynikowym, przeplatanym
kodem Rubiego, umieszczonym w odpowiednich znacznikach. Prosty szablon,
generujący stronę logowania, prezentuje się następująco:
<h1>Logowanie</h1>
<% form_tag session_path, :class=>'style2' do -%>
<p>
<%= label_tag 'login', 'Login' %>
<%= text_field_tag 'login', @login %>
<%= help_tag 'system_elements.session.login.login' %>
</p>
<p>
<%= label_tag 'password', 'Haslo' %>
<%= password_field_tag 'password', nil %>
<%= help_tag 'system_elements.session.login.password' %>
</p>

<p><%= submit_tag 'Zaloguj' %></p>
<% end -%>
Budowa aplikacji
35
W szablonach możliwe jest użycie znaczników
<%= kod_ruby %>
oraz
<% kod ruby %>
Różnica między nimi jest subtelna i polega na umieszczeniu w kodzie
wynikowym wartości wyrażenia pierwszego, natomiast zignorowanie wyniku
drugiego wyrażenia. W znacznikach oprócz zwykłego kodu rubiego, można
umieszczać wywołania metod z Rails, wspomagających generowanie wynikowego
kodu, takimi metodami w powyższym przykładzie są np. form_tag, label_tag czy
też text_field_tag generujące odpowiednio znaczniki HTML <form/>, <label/
>, <input/>.
Szablony operują na danych przekazywanych przez kontroler: wszystkie zmienne
instancji (poprzedzone przedrostkiem „@”) kontrolera są dostępne też dla widoku.
Mają one również dostęp do stałych kontrolera i zmiennych globalnych.
Szablon główny i szablony częściowe
Szablony zaprezentowane w tym rozdziale same w sobie nie generowały
kompletnego kodu HMTL, brak tam takich wymaganych elementów, jak <html/
>, <body/>, czy też definicji stylu wyglądu strony. Elementy te zazwyczaj nie
zmieniają się w ramach aplikacji, stanowią one tzw. layout[1] strony, dlatego
w Rails umieszczono mechanizm pozwalający zdefiniować te elementy tylko raz
w jednym miejscu, a elementy zmienne strony dołączać w ramach potrzeb.
Taki wspólny dla całej aplikacji szablon znajduje się w pliku views/layouts/
application.html.erb, dotyczą go te same zasady co omawianych wcześniej
szablonów, z wyjątkiem instrukcji: <%= yield %>. Odpowiada ona za połączenie
kodu wygenerowanego z szablonu przetwarzanej akcji z kodem szablonu aplikacji.
Innym rodzajem szablonów są szablony częściowe (partials [1]), które stanowią
pewną analogię do metod w obiektach – pozwalają zamknąć część kodu szablonu
w osobnym pliku, by następnie powoływać się na nie z innych miejsc. Szablony
takie, zapisywane w plikach z przedrostkiem _, mogą być wywoływane z poziomu
innego szablonu lub nawet kontrolera. Podobnie jak metody, partiale mogą
przyjmować parametry, które wpływają na ich działanie.
Podstawowym sposobem wywoływania szablonów częściowych jest użycie
metody render z parametrem :partial, np.:
<%= render :partial => 'kind_of_transfer',
:locals => { :transfer => @transfer } %>
Umieszczenie takiego kodu w szablonie powoduje włączenie wyniku działania
szablonu kind_of_transfer do wygenerowanej treści. Użycie w tej metodzie
parametru :locals, powoduje przekazanie do wnętrza szablonu częściowego
zmiennej @transfer.
Dodatkowo istnieje możliwość przekazania parametru :collection, którego
użycie powoduje iteracyjne wykonanie szablonu częściowego dla każdego
elementu podanej jako parametr kolekcji.
36
Rozdział 6
Testowanie aplikacji
Społeczność programistów języka Ruby znana jest jako najbardziej przesiąknięta
ideą testowania. Standardowe biblioteki języka zawierają moduł Test::Unit
a  programiści zachęcani są do jak najszerszego testowania swoich programów
zarówno przez najbardziej znane osoby w społeczności jak i niemal w każdej
książce.
Chociaż sam język Ruby nie jest jeszcze ustandaryzowany (pojawiły się już
propozycje dokonania tego w oparciu o ISO) to istnieje projekt RubySpec, który
stawia sobie za cel utworzenie wykonywalnej specyfikacji dla języka oraz jego
standardowych bibliotek. RubySpec jest głównie rozwijany i wykorzystywany
przez deweloperów różnych implementacji języka (zwłaszcza Rubinius oraz JRuby)
dla zapewnienia ich kompatybilności. Twórcy nowych interpreterów mogą dzięki
bogatemu zestawowi testów sprawdzić, czy zachowują się one w  pożądany
sposób w najróżniejszych sytuacjach oraz mierzyć postępy w pracach.
Nic zatem dziwnego, że to zorientowanie na testowanie głęboko przeniknęło do
Rails i jest uznawane, za jedną z najlepszych praktyk. W tym rozdziale zostaną
zaprezentowane standardowe sposoby testowania aplikacji Ruby on Rails oraz
krótko przedstawione alternatywne rozwiązania.
6.1. Wprowadzenie
Testowanie odbywa się w oparciu o standardową bibliotekę Test::Unit
dostarczaną razem z interpreterem języka. Oznacza to, że programiści języka
Ruby, którzy są z nią zaznajomieni, mogą niemal od razu zacząć pisać testy dla
Rails.
Proces testowania polega na napisaniu metod sprawdzających prawdziwość
pewnych stwierdzeń, bazując przy tym na gotowych zestawach danych. Takie
metody zgrupowane razem w klasie Test::Unit::TestCase tworzą razem
zestaw testów, który może być uruchomiony w każdej chwili. Uruchomienie
testów dostarcza informacji zwrotnej, o powodzeniu lub niepowodzeniu procesu
weryfikacji poszczególnych założeń, które miały być spełnione.
Asercje
Serce całego frameworku testującego stanowią asercje, które weryfikują
prawdziwość oczekiwań programisty względem jakiegoś obiektu lub kodu. Asercje
to po prostu metody wywoływane w testach, przyjmujące jeden lub więcej
parametrów a czasem dodatkowo blok kodu. Jeśli założenie wyrażone przez
asercję nie jest spełnione, następuje przerwanie wykonywania aktualnego testu
i uznaje się, że został on zakończony niepowodzeniem.
Niektóre, najczęściej używane asercje (ostatni opcjonalny parametr oznaczający
komunikat, który ma się pojawić, jeśli nie są one spełnione, został zawsze
pominięty) [12]:
• assert(object);
Testowanie aplikacji
37
Nie zaliczona, gdy parametr object to false lub nil. Przykład:
assert Money.new.empty?
Metoda empty? utworzonego obiektu klasy Money powinna zwrócić true aby
asercja była spełniona.
• assert_nil(object);
assert_not_nil(object);
Przechodzi (nie przechodzi), gdy parametr object ma wartość nil. Przykład:
assert_not_nil Money.new(@zloty => 10).currency
Metoda currency obiektu klasy Money, który przechowuje informację o 10
złotych nie powinna zwrócić nil.
• assert_equal(expected, actual);
assert_not_equal(expected, actual);
Test jest kontynuowany, jeśli parametry expected oraz actual są równe (różne).
Przykład:
assert_equal Money.new(@zloty => 10), Money.new(@zloty => 10)
Dwa obiekty klasy Money przechowujące informację o takiej samej ilości środków
w tej samej walucie powinny być uznane za równe sobie.
• assert_match(pattern, string);
assert_not_match(pattern, string);
Przechodzi, jeśli parametr string zostanie (nie zostanie) dopasowany do
wyrażenia regularnego pattern. Przykład:
assert_match /pust[y|a|e]/, empty_user.errors.on(:login)
Opis błędu pola login, w sytuacji gdy nie jest ono wypełnione powinien zawierać
jedno ze słów: pusty, pusta, puste.
• assert_raise(Exception, ... , &block);
assert_nothing_raised(Exception, ... , &block);
Asercja spełniona, jeśli wykonywany blok kodu rzuci (nie rzuci) jeden
z wymienionych rodzajów wyjątków. Przykład:
assert_nothing_raised do
User.SALDO_CALCULATING_ALGORITHMS.keys.each do |algorithm|
Category.compute(algorithm, @john.categories)
end
end
Obliczenie wszystkimi możliwymi algorytmami salda dla kategorii użytkownika
@john nigdy nie powinno spowodować rzucenia jakiegokolwiek wyjątku. Taki
test nie przejdzie, jeśli programista dodałby nowy algorytm obliczania salda, ale
zapomniałby go zaimplementować.
Testowanie aplikacji
38
Budowa zestawu
Zestaw testów to zbiór metod o nazwach zaczynających się od słowa test
zdefiniowanych w klasie dziedziczącej z Test::Unit::TestCase.
W ciele metody następuje zwykle doprowadzenie obiektu testowanej klasy do
odpowiedniego stanu, a następnie upewnienie się co do jego poprawności,
poprzez wykonanie asercji.
Często wykonanie każdego testu byłoby poprzedzone dokładnie taką samą
procedurą inicjalizacyjną np. tworzone są te same obiekty, których współpraca
jest później testowana. Taki kod umieszcza się wewnątrz metody setup
uruchamianej przed wywołaniem każdego z testów lub metody teardown
wykonywanej po ich zakończeniu, zwykle w celu finalizacji działania pewnych
obiektów np. zamknięcia deskryptorów plików [1].
Przykładowy zestaw dwóch testów dla klasy Money przechowującej informacje
o środkach pieniężnych w różnych walutach:
class MoneyTest < Test::Unit::TestCase
def setup
save_common_currencies
end

def test_initialize
assert_nothing_raised do
Money.new(@zloty => 10, @dolar => 15)
Money.new(@zloty, 10)
Money.new()
end
assert_raise ArgumentError do
Money.new(1)
end
end
def test_emptyness
money = Money.new()
assert money.is_empty?
money.add!(10, @zloty)
assert !money.is_empty?
money.add!(-10, @zloty)
assert money.is_empty?
end

end

W zestawie tym przed uruchomieniem obu metod testujących wywoływana
jest metoda setup, a w niej save_common_currencies tworząca standardowe
waluty przypisane do zmiennych @zloty oraz @dolar. Metoda test_initialize
Testowanie aplikacji
39
sprawdza czy tworzenie nowych obiektów klasy Money z użyciem poprawnych
konstruktorów nie spowoduje powstania wyjątku. Użycie niepoprawnych powinno
spowodować rzucenie wyjątku typu ArgumentError. Wewnątrz test_emptyness
sprawdzane jest, czy wywołania metody empty? zwracają odpowiednio true
i  false w zależności od tego czy obiekt klasy Money jest pusty, czy też
przechowuje informacje o środkach w walucie @zloty.
Uruchamianie
Wykonywanie testów możliwe jest zarówno na najniższym poziomie (pojedyncza
metoda z jednego zestawu np. takiego, jak opisany w poprzednim podrozdziale):
app> ruby -I test test/unit/money_test.rb --name "test_emptyness"
jak i coraz wyższych:
• Jeden zestaw dla konkretnej jednostki, zwykle klasy (tutaj cały zestaw testów
klasy Money):
app> ruby -I test test/unit/money_test.rb
• Wszystkie zestawy z konkretnej grupy testów (jednostkowe, funkcjonalne,
integracyjne):
app> rake test:units
app> rake test:functionals
app> rake test:integration
• Wszystkie testy dostępne dla aplikacji:
app> rake test
W wyniku wykonania jednej z takich komend, zostaną uruchomione testy a ich
rezultat jest prezentowany na ekranie:
• Wynik negatywny jednego z testów. Pojawia się informacja o nazwie testu
zakończonego niepowodzeniem oraz numer linii kodu, gdzie znajdowała się
niezgodna z prawdą asercja. Niepomyślne wykonanie oznaczane jest literą "F"
na pasku postępu.
Started
...................F..... itd.
Finished in 97.429342 seconds.
1) Failure:
test_memcached_write_and_read(MemcachedTest)
[/test/unit/memcached_test.rb:7]:
<true> expected but was <false>.
190 tests, 1296 assertions, 1 failures, 0 errors
Command failed with status (1)
• Wszystkie testy zakończone prawidłowo. Pomyślne wykonanie oznaczane jest
kropką.
Testowanie aplikacji
40
Started
......................... itd.
Finished in 96.124058 seconds.
190 tests, 1297 assertions, 0 failures, 0 errors
Tworzenie szkieletu testów
Ilekroć korzystamy z dostępnych generatorów kodu, w odpowiednich katalogach
tworzone są także pliki zawierające szkielet testów dla tworzonych obiektów:
app> script/generate model user &&
script/generate controller session
. . .
. . .
create app/models/user.rb
create test/unit/user_test.rb
. . .
. . .
create app/controllers/session_controller.rb
create test/functional/session_controller_test.rb
Możliwe jest również dodatkowe wygenerowanie potrzebnych plików:
app> script/generate integration_test save_loan_and_send_remind
exists test/integration/
create test/integration/save_loan_and_send_remind_test.rb
6.2. Testy jednostkowe
Testy jednostkowe stanowią narzędzie weryfikacji logiki biznesowej [1]. W aplikacji
Manage My Money najczęściej przy ich użyciu kontroli podlegają takie obszary jak:
• reguły walidacji obiektów biznesowych,
• prawidłowość dokonywanych obliczeń,
• zachowanie obiektów wobec nieprawidłowych danych.
Zgodnie z konwencją każdej klasie z dziedziny modelu odpowiada jeden zestaw
testów sprawdzających jej poprawność oraz (w mniejszym stopniu) stanowiących
specyfikację jej zachowania.
Testy jednostkowe zawarte są w klasie ActiveSupport::TestCase, która
udostępnia kilka dodatkowych funkcjonalności w porównania z jej nadklasą
Test:Unit:TestCase takich jak:
• Użycie deklaratywnej składni:
test "Saldo is empty before creating transactions" do
@john.categories.each{|category| assert category.saldo.empty?}
end
Testowanie aplikacji
41
zamiast definiowania metod:
def test_saldo_is_empty
@john.categories.each{|category| assert category.saldo.empty?}
end
• Korzystanie z dodatkowych asercji:
• assert_difference(expressions, difference = 1, &block);
assert_no_difference(expressions, &block);
Przechodzi gdy wartość wyrażenia expression zmieni się o wartość
parametru difference (nie zmieni się), w wyniku wykonania podanego bloku
kodu. Przykład:
assert_difference("@john.categories.count", -1) do
@john.categories.first.destroy
end
Wartość wyrażenia @john.categories.count oznaczającego liczbę kategorii
należących do użytkownika @john powinna być o 1 mniejsza po wykonaniu
kodu @john.categories.first.destroy usuwającego pierwszą kategorię
tego użytkownika.
• assert_valid(activerecord_object);
Zaliczona, jeśli parametr activerecord_object jest obiektem, w którym
spełnione są wszystkie reguły walidacyjne.
• Dodawanie callbacków dla testów (wykonywanych przed uruchomieniem,
lub po zakończeniu każdego z nich) poprzez wywołanie metody setup lub
teardown na poziomie klasy i podanie symbolu oznaczającego nazwę metody
do wykonania. Zaletą korzystania z tej implementacji jest automatyczne
wykonywanie callbacków z nadklasy we właściwej kolejności:
class CategoryTest < ActiveSupport::TestCase
setup :save_currencies
setup :save_users
end
zamiast
class CategoryTest < ActiveSupport::TestCase
def setup
super
save_currencies
save_users
end
end
6.3. Testy funkcjonalne
Kontrolery nadzorują pracę całej aplikacji poprzez przyjmowanie żądań,
współpracę z modelem a następnie wysyłanie odpowiedzi. Testowanie
Testowanie aplikacji
42
kontrolerów oznacza zatem upewnienie się, że odpowiednie żądania zostaną
obsłużone w odpowiedni sposób (np. pod względem dostarczanej treści, która
zwykle jest dynamicznie generowana).
Podstawy
Wewnątrz testu dziedziczącego po klasie ActionController::TestCase możemy
używać kilku metod (get, post, put, delete, head) o nazwach odpowiadającym
żądaniom HTTP, symulując w ten sposób różne działania użytkownika w serwisie.
Argumenty tych metod w prosty sposób pozwalają na imitowanie sytuacji, w jakiej
był użytkownik, przed wykonaniem testowanej akcji w przeglądarce:
get(action, parameters = nil, session = nil, flash = nil);
• action - testowana metoda kontrolera,
• parameters - opcjonalny hash z parametrami żądania HTTP np. dane wysłane
przez użytkownika w formularzu,
• session - stan sesji,
• flash - hash z obiektami (np. wiadomościami) przekazywanymi tylko do
następnego żądania.
Przykładowo:
get :index
post :create, :user => { :name => "dave" , :age => "24" }
Do dyspozycji jest też metoda xhr, która służy do testowania żądań typu
XmlHttpRequest wysłanych np. z użyciem kodu JavaScript osadzonego na stronie.
W serwisie Manage My Money ma to miejsce np. kiedy wysyłany jest tekst
wprowadzonego opisu transakcji a serwer ma zwrócić dostępne podpowiedzi
do wpisywanego tekstu. Pierwszy parametr tej metody oznacza rodzaj żądania
(:get, :post, :put, :delete, :head) a następne są takie same jak opisane wcześniej
dla poprzednich metod.
Do sprawdzania statusu odpowiedzi używa się metody assert_response, która
może przyjąć zarówno oczekiwany numer, jak i opisowy symbol np.:
assert_response 200
assert_response :redirect
assert_response :unauthorized
W przypadku gdy odpowiedzią jest przekierowanie dobrze wiedzieć, czy kieruje
ono użytkownika we właściwe miejsce:
assert_redirected_to :controller => :session , :action => :new
assert_redirected_to "http://example.org/login"
Możemy się też upewnić co do szablonu, jaki został użyty do wygenerowania kodu
strony:
assert_template 'index'
assert_template 'empty_list'
Testowanie aplikacji
43
Korzystając z metod session , flash oraz cookies możemy sprawdzić stan
sesji, obiektu flash oraz zawartość jaką miałyby pliki cookies. Może to być
przydatne np. gdy chcemy sprawdzić, czy sesja zawiera identyfikator użytkownika
po zalogowaniu się przez niego:
post :create, :login => @john.name, :password => 'valid password'
assert_equal @john.id, session[:user_id]
Ostatnia ważna i często używana metoda to assigns, która pozwala odczytać
zmienną instancji kontrolera o podanej nazwie. W poniższym przypadku
upewniamy się, że przy próbie pobrania listy walut, instancja kontrolera
CurrenciesController przypisała wartość do zmiennej @currencies, z której
korzysta widok podczas generowania strony HTML.
class CurrenciesControllerTest < ActionController::TestCase
def test_index
get :index
assert_response :success
assert_template 'index'
assert_not_nil assigns(:currencies)
end
end
Do poprawnego działania wspomnianych metod, w czasie wykonywania testów,
muszą być odpowiednio ustawione zmienne @controller, @request oraz
@response. W przeszłości trzeba było je zdefiniować w metodzie setup
uruchamianej przed każdym z nich np.:
class CurrenciesControllerTest < ActionController::TestCase
def setup
@controller = CurrenciesController.new
@request = TestRequest.new
@response = TestResponse.new
end
end
Aktualnie jednak nie jest to już konieczne, gdyż klasa
ActionController::TestCase definiuje callback
setup :setup_controller_request_and_response
ustawiający te zmienne. W szczególności tworzy nową instancję kontrolera na
podstawie nazwy klasy agregującej testy (CurrenciesControllerTest). Jeśli
jednak nazwa odbiega od konwencji można wyraźnie wskazać, jaki kontroler ma
być testowany poprzez użycie metody tests:
class SecurityCurrenciesControllerTest < ActionController::TestCase
tests CurrenciesController
end
Tak rozbudowany arsenał dostępnych środków pozwala pisać zarówno proste jak
i bardziej rozbudowane testy o bardzo dużym stopniu czytelności:
class CurrenciesControllerTest < ActionController::TestCase
Testowanie aplikacji
44
def test_create_currency_without_errors
log_john
assert_difference("@john.currencies.count", +1) do
post :create, :currency => {
:long_symbol => 'CHF'
:symbol => 'CHF',
:long_name => 'frank szwajcarski',
:name => 'frank'
}
assert_response :redirect
assert_redirected_to :action => :index
assert_match /Utworzono/, flash[:notice]
end
end
end
Ten test sprawdza czy po wysłaniu przez zalogowanego użytkownika @john
żądania HTTP typu POST z danymi formularza nt. nowo tworzonej waluty zostanie
on przekierowany na listę dostępnych walut wraz z komunikatem, że utworzono
nowy obiekt. Zapewnia on także, że liczba walut użytkownika wzrosła o 1.
Testy bezpieczeństwa
Testy bezpieczeństwa dla kontrolerów mają na celu zweryfikowanie, czy
prawidłowo działają ograniczenia w dostępności do elementów systemu
utworzonych przez innych użytkowników.
Jednym z przykładowych sposobów, w jaki użytkownik mógłby chcieć spróbować
obejrzeć lub edytować dane innych osób, jest samodzielne przejście pod inny
adres w przeglądarce lub wysłanie odpowiednio spreparowanego żądania HTTP
np. korzystając z uniksowego programu curl.
Listing testu sprawdzającego, czy kontroler odpowiedzialny za zarządzanie
kategoriami, nie jest podatny na tego typu atak:
class CategoriesControllerTest < ActionController::TestCase
def setup
save_users(:john, :kate)
log_john
end
def test_invisible_to_others
[[:show,:get],
[:search, :post],
[:destroy, :delete],
[:edit, :get],
[:update, :put]].each do |action, method|
send(method, action, :id => @kate.asset.id)
assert_redirected_to :action => :index
assert_match("Brak uprawnien", flash[:notice])
Testowanie aplikacji
45
end
end
end
Werfyikuje on czy zalogowany użytkownik @john podejmując jakąkolwiek
akcję (podgląd, edycja, zapis zmian, usunięcie, wyszukiwanie) wobec kategorii
użytkowniczki @kate, zostanie przekierowany z powrotem do listy swoich kategorii
z komunikatem, że brak mu uprawnień do wykonania niedozwolonej akcji.
6.4. Inne rodzaje testów
Możliwe jest tworzenie także kilku innych rodzajów testów. Są one jednak rzadziej
spotykane lub często implementowane przy użyciu alternatywnych zamienników,
dlatego zostaną omówione tylko pokrótce.
Testy integracyjne
Testy integracyjne obejmują wiele kontrolerów oraz akcji w celu zapewnienia,
że współpracują one razem w oczekiwany sposób [1]. Najbardziej przekrojowo
testują one całą aplikację od wysyłania żądań po bazę danych. Mogą one
zostać użyte do testowania scenariuszy, w których użytkownik osiąga pewien
rezultat poprzez wykonanie ciągu wielu różnych akcji np. przejście przez cały
proces dokonywania zakupów od logowania, poprzez dodawanie przedmiotów
do koszyka, aż po dokonanie płatności. Składniowo testy integracyjne są
podobne do funkcjonalnych. W aplikacji Manage My Money skorzystaliśmy z nich
w bardzo niewielkim stopniu, gdyż wysokopoziomowe testowanie aplikacji zostało
zrealizowane z użyciem Selenium.
Testy routingu
Istnieje możliwość napisania testów jednostkowych, które będą koncentrować
się na sprawdzaniu prawidłowości mapowania adresów stron na wywołania akcji
w kontrolerach. Używa się w tym celu odpowiednich asercji: assert_generates,
assert_recognizes oraz assert_routing [1]. Jednak budowanie aplikacji
w oparciu o konwencję REST niemal całkowicie eliminuje konieczność testowania
routingu. Wydaje się to być sensowne tylko w zaawansowanych przypadkach.
Testy wydajnościowe
Przy użyciu klas Benchmark oraz ActionView::Helpers::BenchmarkHelper bada
się czas w jakim wykonywane są poszczególne zadania w testach [1]. Rails
dostarcza także skrypty służące za profiler i benchmark, którymi można ad hoc
sprawdzać dowolny kod.
Ponieważ jednak testowanie w odizolowanym środowisku potrafi wskazywać
zupełnie inne rezultaty niż w środowisku produkcyjnym, nie jest to bardzo
zalecana technika i należy używać jej ostrożnie. Trzeba też pamiętać, że maszyny
programistów mogą dalece różnić się wydajnością i z tego powodu ustalenie
odgórnych ograniczeń na czas wykonania kodu może być trudne oraz powodować
nieuzasadnione błędy, podczas uruchamiania testów na wolniejszej maszynie.
Z tego powodu raczej korzysta się z adapterów na bieżąco zbierających
dane o wydajności w środowisku produkcyjnym lub stara się je wydobyć
Testowanie aplikacji
46
z  logów. Wdrożenie w domenie co-do-grosza.pl jest nadzorowane przy użyciu
oprogramowania New Relic RPM, które umożliwia bieżące przeglądanie statystyk
z poziomu przeglądarki.
6.5. Narzędzie alternatywne oraz dodatkowe
Ze względu na łatwą rozszerzalność środowiska Ruby on Rails poprzez gemy
oraz pluginy a także odmienność technik programistycznych używanych przy
realizacji projektów istnieje wiele narzędzi mających wspomagać testowanie
aplikacji. Niektóre z nich zdobyły już bardzo silną pozycję [13] a ich znajomość
jest wymagana lub bardzo pożądana przez pracodawców:
• RSpec - alternatywny framework do testowania oraz specyfikowania zachowań
aplikacji powstały jako narzędzie do realizacji projektów zgodnie z BDD.
Zamiennik dla Test::Unit.
• Cucumber - narzędzie do wykonywania testów funkcjonalnych, opisujących
działanie aplikacji w postaci plików tekstowych (ich składnia oparta jest o język
Gherkin) [14]. Pliki te zawierają scenariusze, składające się z opisanych w
języku naturalnym kroków, którym odpowiadają napisane w języku Ruby akcje
do wykonania.
• Selenium - rozbudowany zestaw narzędzi (Core, IDE, RC, Grid) do testowania
aplikacji webowych w przeglądarkach internetowych. Może się ono odbywać
w różnych systemach operacyjnych oraz przeglądarkach (pod warunkiem, że
wspierają one JavaScript) na trzy sposoby [15]:
1. Do aplikacji testującej (Selenium Core), działającej w środowisku przeglądarki
dostarczany jest plik określający poszczególne kroki, jakie mają być
wykonane np. przejście pod odpowiedni adres, wciśnięcie przycisku, wpisanie
tekstu w polu formularza. Poszczególne kroki i asercje są przetwarzane,
a w przypadku niepowodzenia test jest przerywany. Istotnym ograniczeniem
tego narzędzia jest fakt, że zbiór wszystkich komend do wykonania musi
być załadowany od razu, co nie pozwala np. na dynamiczne generowanie
następnego polecenia na podstawie identyfikatora obiektu zapisanego
w bazie, w czasie wykonywania poprzednich instrukcji.
Wspomaganiem testowania aplikacji Rails z użyciem selenium zajmuje się
plugin selenium-on-rails, który dostarcza kilka wygodnych usprawnień
m.in. przyjemniejszą składnię (formaty .sel oraz .rsel).
2. Przy użyciu dodatkowego komponentu jakim jest Selenium Remote Control
(RC). Zbudowany jest on z:
• serwera działającego jako proxy dla żądań HTTP oraz zarządzającego
uruchamianiem i wyłączaniem przeglądarki, w której odbywają się testy.
• bibliotek dla języka programowania używanego do sterowania testami
(możliwe jest używanie innego niż ten, w którym piszemy aplikację).
Wspierane języki to: Java, Ruby, Python, Perl, PHP oraz platforma .Net .
Odmienny sposób działania RC powoduje, że testy nie są całkowicie
wykonywane tylko w przeglądarce, lecz mogą korzystać z modelu
aplikacji oraz wszystkich dobrodziejstw frameworku testującego, który je
Testowanie aplikacji
47
aktualnie wykonuje. Poszczególne asercje do sprawdzenia są przesyłane do
przeglądarki na bieżąco a pomiędzy nimi może być wykonywany dowolny
kod aplikacji oraz inne asercje weryfikujące np. stan obiektów biznesowych
zamiast tylko widok strony WWW, jak to ma miejsce w pierwszym, opisanym
wariancie.
Te liczne możliwości wiążą się jednak z dużo większą liczbą komponentów
używanych do testowania oraz bardziej rozbudowaną i skomplikowaną
konfiguracją. Czas wykonywania takich testów również ulega wydłużeniu
(zakończenie kilku napisanych testów aplikacji Manage My Money z użyciem
RC zajmuje około 5 minut). Z pomocą przy korzystaniu z Selenium RC
przychodzi gem selenium-client.
3. Korzystając z Selenium Grid, które pozwala na wielu maszynach
(w szczególności jednej) uruchamiać zarówno kilka instancji Selenium
RC jak i przeglądarek, przez co możliwe jest szybsze zakończenie
testów oraz dokładne sprawdzenie poprawności działania aplikacji
w różnych środowiskach (systemach operacyjnych, przeglądarkach, wersjach
oprogramowania).
• Webrat - narzędzie do pisania i wykonywania testów akceptacyjnych. Przy
użyciu jednolitego API, umożliwia testowanie poprzez symulowanie działania
przeglądarki (szybkość) lub rzeczywiste jej uruchomienie z pomocą Selenium
(konieczność testowania kodu JavaScript lub wywołań Ajax).
• Shoulda - gem zawierający dodatkowe makra oraz asercje do testowania
najczęściej pojawiających się konstrukcji w modelach i kontrolerach. Umożliwia
także pisanie i wykonywanie testów przy użyciu czytelniejszej składni.
• Mocha oraz RR - wspomagają korzystanie z takich technik jak mocking oraz
stubbing.
• Factory girl - zamiennik dla standardowo dostępnego w Rails mechanizmu
ładowania obiektów do bazy danych, w celu ich wykorzystania w wykonywanych
testach.
48
Rozdział 7
Wdrożenie
Celem twórców aplikacji internetowych jest oczywiście udostępnienie ich dzieła
jak najszerszemu gronu odbiorców. Wymaga to od nich uruchomienia aplikacji
na publicznie dostępnym serwerze w środowisku spełniającym wszystkie warunki
wymagane przez aplikację odnośnie dostępnych komponentów, z którymi ma ona
współpracować. Każde wdrożenie ze względu na unikatowy charakter programu
jest procesem niepowtarzalnym i nie dającym się bardzo łatwo przenieść na
grunt innej aplikacji. W tym rozdziale opiszemy nasze doświadczenia z wdrażania
stworzonego przez nas systemu Manage My Money, użyte narzędzia oraz
zastosowane konfiguracje. Wspomniane zostaną ograniczenia jakim podlegaliśmy
w całym procesie i skutki jakie one wywarły.
7.1. Dostępne opcje
Istnieje kilka możliwości uruchamiania dynamicznych aplikacji zbudowanych
w oparciu o Ruby on Rails. Różnią się one między sobą wieloma aspektami, jednak
to co będzie nas najbardziej interesować to prostota konfiguracji, stabilność oraz
wydajność. Przedstawione w tym rozdziale rozwiązania pozwolą lepiej zrozumieć,
opisaną w dalszej części architekturę stworzonego systemu.
Common Gateway Interface
Common Gateway Interface (CGI) jest opracowanym w 1993 roku interfejsem
umożliwiającym wymianę informacji pomiędzy serwerem WWW oraz innymi
programami znajdującymi się na serwerze. Wysłane przez użytkownika dane
są przetwarzane przez serwer WWW, a następnie dostarczane do programu
(zwykle będącego skryptem jakiegoś interpretowanego języka np. Perl) poprzez
standardowe wejście oraz specjalne zmienne środowiskowe. Po zakończeniu
swojego działania taki program wysyła wynik na standardowe wyjście, skąd jest
on pobierany przez serwer i poddawany dalszym operacjom [16].
Ten sposób działania aplikacji jest bardzo niezalecany ze względu na jego kiepską
wydajności. Bardzo bogate środowisko Ruby on Rails rozrasta się jeszcze bardziej
wraz z wykorzystywanymi przez aplikację pluginami oraz gemami. Specyfika
działania programów w trybie CGI powoduje, że z każdym przychodzącym
do serwera żądaniem należałoby utworzyć nowy proces, który następnie
interpretowałby od nowa kod frameworka oraz aplikacji. Całość trwa na tyle długo,
że możemy uznać tą przestarzałą metodę, która ma swoja korzenie w początkach
dynamicznego internetu, za zupełnie bezużyteczną dla rozważanych przez nas
zastosowań [2].
Lighttpd/Apache
+         
FastCGI        
FastCGI, w przeciwieństwie do CGI, nie tworzy nowego procesu dla każdego
żądania, lecz dysponuję ich pulą, z której wybierany jest jeden do jego obsługi.
Proces ten po obsłużeniu żądania trwa dalej i oczekuje na kolejne. Oznacza to,
że nie jest konieczne nieustanne tworzenie i usuwanie procesów przez system
Wdrożenie
49
operacyjny, co na bardziej obciążonych maszynach zajmowało istotną część czasu
pracy procesora. Ponadto, trwanie procesu umożliwia powtórne wykorzystanie już
nawiązanych połączeń z bazą danych oraz cachowanie obiektów w jego pamięci.
W początkowym okresie istnienia Rails promowano używanie serwera Lighttpd
pracującego w trybie FastCGI, jednak okazało się, że to rozwiązanie generuje
różne problemy oraz narzekano na obsługę load balancingu. FastCGI nie doczekał
się też dobrej implementacji w Apache'u [2].
mod_ruby
Mod_ruby jest opcjonalnym modułem dla serwera Apache, który rozszerza jego
funkcjonalność, o możliwość interpretowania kodu napisanego w języku Ruby.
Skrypty działające pod kontrolą takiego modułu uruchamiają się dużo szybciej,
ponieważ nie ma konieczności każdorazowo od nowa uruchamiać interpretera
języka. Moduł ten jednak nie osiągnął jakości, jakiej od niego oczekiwano
[2] oraz posiada bardzo dużą wadę, jaką jest brak bezpieczeństwa, podczas
jednoczesnego działania wielu różnych aplikacji Ruby on Rails, gdyż mogą one
zacząć współdzielić między sobą klasy [17]. Może on zatem stanowić jedynie
rozwiązanie, dla osób mogących sobie pozwolić na komfort korzystania z osobnej
instancji serwera Apache dla każdej rozwijanej aplikacji, jednak dla większości jest
to rozwiązanie nie do przyjęcia.
Apache / Nginx
+       
mongrel_cluster / Thin / Ebb      
Apache - to otwarty serwer HTTP dostępny dla wielu systemów operacyjnych,
aktualnie rozwijany przez Apache Software Foundation. Najszerzej spotykany
serwer HTTP w Internecie (46% udziału w rynku - maj 2009) [18], który przez
lata dowiódł swoje stabilności i niezawodności. Jego bogate opcje konfiguracyjne
oraz bardzo liczna gama dodatkowych modułów rozwiązujących wiele problemów
sprawiają, iż jest on bardzo często używany przez firmy hostingowe, dla swoich
klientów. Nginx to zdobywający sobie coraz większą popularność serwer HTTP
(wg. badań z maja 2009 roku serwował on 2.8 miliona stron)[18] napisany przez
Rosjanina Igora Sysoeva, który rywalizuje z Apachem szybkością, mniejszym
zużyciem pamięci oraz łatwiejsza składnią konfiguracji.
Mongrel, Thin oraz Ebb to serwery przetwarzające kod Ruby'ego. W tej roli
spisują się bardzo dobrze, jednak nie ma sensu ich obciążać żądaniami do
plików statycznych (obrazki, style, pliki binarne), których nigdy nie będą w stanie
obsłużyć tak szybko jak napisane w języku C, specjalizowane serwery klasy
Apache czy Nginx.
Dlatego, w celu osiągnięcia jak największej wydajności, stosuje się rozwiązanie
hybrydowe. Postawiony z przodu serwer HTTP (Apache, Nginx) obsługuje żądania
do plików statycznych, natomiast gdy przychodzi prośba wymagająca użycia
Ruby'ego, jest ona przekierowywana do jednego z kilku serwerów, które to
potrafią (wykorzystywany jest w tym celu load balancing, czyli mechanizm
równomiernego rozkładania obciążenia pomiędzy wiele uruchomionych instancji
aplikacji).
Mongrel jest zarówno biblioteką jak i serwerem HTTP zaprojektowanym jako
alternatywa dla FastCGI. Większość jego kodu została napisana w Ruby, pozostała
Wdrożenie
50
część w języku C. Do komunikacji z innymi serwerami, które mogą stanowić
warstwę poprzedzającą używa protokołu HTTP zamiast FastCGI czy SCGI. Poza
Rails wspiera także kilka innych frameworków.
Thin jest serwerem, który został oparty o trzy biblioteki:
• Mongrel parser - składowa mongrela, odpowiedzialna za jego wysoką stabilność
i szybkość pracy.
• Event Machine - wysoce skalowalna i stabilna, asynchroniczna biblioteka I/O dla
aplikacji sieciowych.
• Rack - minimalny interfejs do pracy między serwerami webowymi
a frameworkami używającymi Ruby, powstały na bazie inspiracji pythonowym
modułem WSGI.
Najmłodszy z nich czyli Ebb, to mały i szybki serwer HTTP, napisany całkowicie
w języku C. Każdy z nich ma możliwość pracy jako klaster kilku procesów.
Dedykowane serwery do przetwarzania Ruby'ego uczyniły wdrażanie aplikacji
opartych o Rails zdecydowanie prostszym. Dużo mniejsza problematyczność tego
rozwiązania oraz możliwość prostego skalowania aplikacji w poziomie (poprzez
dołożenie kolejnego serwera) spowodowały wyraźne odejście od korzystania
z wcześniej polecanego FastCGI na rzecz opisanych powyżej rozwiązań.
Phusion Passenger
(mod_rails)        
Wyznacznikiem łatwości instalacji aplikacji webowej są z pewnością systemy
oparte o PHP. Do ich zainstalowania wystarczy wykupienie hostingu u dostawcy
oraz przegranie plików do odpowiedniego folderu a następnie skonfigurowanie
bazy danych na stronie internetowej, pod którą dostępna jest uruchamiana
aplikacja.
Twórcy modułu Phusion Passenger (rozszerzenia dostępnego dla Apache oraz od
niedawna także Nginx) postawili sobie za cel osiągnięcie takiej samej prostoty we
wdrażaniu aplikacji opartych o Rails, tak by nie było konieczności konfigurowania,
nadzorowania i restartowania kilku różnych usług dla każdego wdrożenia. Ich
praca została bardzo pozytywnie odebrana w świecie Rubiego. Wiele osób uważa,
że ten sposób szybko zdobędzie sobie pierwszeństwo, natomiast korzystanie
z  serwerów takich jak mongrel, thin, czy ebb będzie zarezerwowane raczej dla
sytuacji wymagających jak największej wydajności kosztem mniejszej prostoty.
Wdrażanie aplikacji na własne serwery (rozumiane jako takie, gdzie posiadamy
dostęp do pełnych uprawń administratora, czyli praktycznie wszystkie
rozwiązania poza hostingiem dzielonym) było łatwe od momentu pojawienia
się mongrela i jego następców. Jednak zaoferowanie hostingu dla Ruby on
Rails przez firmy było bardzo trudne, aż do chwili zaistnienia mod_rails, gdyż
wymagało udostępniania klientom możliwości logowania się przez połączenia
SSH, uruchamiania własnych procesów oraz co się z tym wiązało dużego nakładu
pracy, położonego na bezpieczeństwo tych rozwiązań, które dawały klientom
sporą swobodę działania na serwerach usługodawcy. Z tego powodu wdrażanie
małej aplikacji, start-upu wiązało się z koniecznością posiadania własnego
Wdrożenie
51
serwera, który mogliśmy dowolnie konfigurować (jednak musieliśmy też umieć
go odpowiednio zabezpieczyć) lub poszukiwania specjalnych rozwiązań wśród
nielicznych firm oferujących takowe. W obu przypadkach problemem zwykle
pozostawała cena. Phusion Passenger daję nadzieję, że w najbliższej przyszłości
cała procedura znacznie się uprości a cena zmaleje, gdyż wielu dostawców będzie
mogło w prosty sposób zaoferować hosting dla Ruby on Rails oparty o moduł,
bardzo wygodny dla nich oraz dla klientów. Byłby to moment, kiedy zostałaby
usunięta spora bariera ograniczająca rozwój Rails, podobnie jak miało to miejsce
w przypadku ASP .NET, co może mieć pozytywny wpływ na popularyzację tego
rozwiązania.
Phussion Passenger ma kilka zalet, które sprawiają, iż wdrażanie z jego użyciem
jest dużo łatwiejsze [17][19]:
• Nie trzeba nadzorować serwerów odpowiedzialnych za przetwarzanie kodu
Ruby. Do takich zadań używa się programów takich jak monit lub god.
Passenger potrafi samodzielnie stwierdzić, że jakiś proces przestał odpowiadać
i utworzyć nowy.
• Brak sztywno określonej liczby instancji aplikacji. Są one dodawane bądź
odejmowane dynamicznie w zależności od zapotrzebowania na nie, co oznacza
lepsze zarządzanie pamięcią na serwerze.
• Zapytania są wysyłane do procesów, które mają najmniej klientów w kolejce
(można też skorzystać z globalnego kolejkowania, przydatnego gdy aplikacja
często obsługuje długie żądania).
• Współpraca z interpreterem Ruby Enterprise napisanym przez tych samych
autorów, który pozwala zaoszczędzić około 33% pamięci.
W standardowym scenariuszu każdy z procesów odpowiedzialnych za
przetwarzanie kodu Ruby jest tworzony osobno i nie współdzielą one żadnych
danych. Passenger kiedy korzysta z interpretera Ruby Enterprise używa innej
techniki: najpierw uruchamiany jest jeden proces, który ładuje środowisko
a  następnie wykonywany jest jego fork. System operacyjny używa wtedy
techniki Copy on Write, która sprawia, że tak długo jak dane są tylko czytane,
tylko jedna ich kopia pozostaje w pamięci i jest używana przez oba procesy.
Dzięki takiemu podejściu instancje aplikacji mogą współdzielić zwłaszcza kod
frameworka i bibliotek z których korzystają [19][17].
Aby osiągnąć zamierzony rezultat, należało uczynić garbage collector języka
przyjaznym dla techniki COW, poprzez używanie zbioru pól bitowych (set
of marking bitfields) zamiast flagi wewnątrz obiektu, oznaczającej czy jest
on osiągalny, czy też powinien zostać usunięty z pamięci. Ustawianie przez
garbage collector flagi powodowało, że dane były kopiowane i  przestawały
być współdzielone przez procesy, co nie ma miejsca w wersji Enterprise, gdzie
informacja ta jest odizolowana od samego obiektu [19][20].
7.2. Platforma wdrożeniowa
Tworząc aplikację Manage My Money nie mieliśmy przekonania, że jest ona
w  stanie w polskich warunkach wygenerować sensowny dochód. Wobec licznej
i  często dużo lepiej zorganizowanej konkurencji (pod względem strukturalnym
Wdrożenie
52
oraz posiadanych zasobów) chcieliśmy tylko znaleźć odpowiednią niszę, która
jednak naszym zdaniem i tak nie jest jeszcze w stanie być dochodowa.
Pragneliśmy zatem, by zainwestowane przez nas środki finansowe, poświęcone na
realizację tego projektu, utrzymały się na jak najniższym poziomie. Ograniczenie
to było głównym kryterium zawężającym wybór sposobu i miejsca wdrożenia
systemu.
Skoro nie mogliśmy sobie pozwolić na komfort posiadania własnego serwera,
którego konfiguracja i parametry byłyby całkowicie pod naszą kontrolą, co
znacznie ułatwiłoby wdrożenie dając nam możliwość łatwego i szybkiego
doinstalowywania potrzebnych komponentów a także ich pełnej konfiguracji,
pozostało nam jedynie skorzystanie z płatnego hostingu. Przedział cenowy
oferowanych rozwiązań był szeroki, jednak w większości były one dedykowane
dla firm i przerastały nasze skromne możliwości oraz oferowały wygórowane
parametry. Zdecydowaliśmy się skorzystać z najtańszej wtedy oferty dla kont
shellowych prezentowanej na rootnode.net. Usługę mieliśmy zakupioną jeszcze
z czasów, gdy oferowana była ona przez stowarzyszenie, a nie firmę.
Apache
W procesie przetwarzania żądania od użytkownika, odwiedzającego witrynę co-
do-grosza.pl, biorą udział dwie maszyny. Na początku dochodzi ono do komputera
o nazwie Venema, na którym działa serwer Apache. Jest on przystosowany
do obsługi aplikację Ruby on Rails, z użyciem opisanego wcześniej modułu
Passenger, jednak nie udało nam się skorzystać z tego rozwiązania, ze względu
na rozbieżność zainstalowanych na nim wersji oprogramowania w stosunku do
naszych wymagań. Zdecydowaliśmy się więc na inne podejście, dlatego rola tego
serwera sprowadza się tylko do przekazania żądania do drugiej maszyny o nazwie
Stallman. Służy nam do tego zdefiniowany w katalogu htdocs (katalog główny
dla dokumentów witryny) plik .htaccess o następującej treści:
RewriteEngine on
RewriteRule ^(.*)$ http://s.co-do-grosza.pl:3030/$1 [P,L]
Takie ustawienia powoduje, że każde zapytanie postaci http://co-do-grosza.pl/
X/Y/Z/... zostanie przekształcone na http://s.co-do-grosza.pl:3030/X/Y/Z/... przy
użyciu modułu mod_rewrite, który służy do zamiany adresów internetowych
po stronie serwera na podstawie reguł i wyrażeń regularnych. Litera P
oznacza, że mamy do czynienia z  proxy, natomiast L, że należy skończyć
przetwarzanie po zastosowaniu tej reguły. Ponieważ subdomena s.co-do-grosza.pl
jest skonfigurowana tak, by wskazywała na maszynę Stallman, w wyniku
zastosowania tej reguły każde żądanie zostaje przekierowane właśnie tam,
konkretnie na port 3030, gdzie nasłuchuje aplikacja Pound.
Wdrożenie
53
Rysunek 7.1. Architektura systemu
Pound
Pound to bardzo mały program, który służy nam jako load-balancer i rozkłada
obciążenie do czterech procesów mongrel, z których każdy działa na innym porcie.
Konfiguracja takiej funkcjonalności wygląda następująco:
User "deploy"
Group "users"
ListenHTTP
Address 0.0.0.0
Port 3030
Service
BackEnd
Address 127.0.0.1
Port 3040
End
BackEnd
Address 127.0.0.1
Port 3039
End
# Analogicznie dla portów 3038 oraz 3037
End
End
Pound potrafi przekierować ruch do innej maszyny/procesu niż pierwotnie
wylosowana dla danego żądania w przypadku, gdyby ona nie odpowiadała
(np. w czasie awarii). Posiada sporo opcji umożliwiających wybór docelowego
adresu na podstawie różnych kryteriów, jednak nam wystarczyły najprostsze
ustawienia. Gdybyśmy dysponowali własny serwerem moglibyśmy balansowanie
ruchu ustawić na poziomie konfiguracji hosta dla serwera Apache, jednak na
serwerarch Venema oraz Stallman nie mieliśmy praw dostępu do odpowiednich
plików, co zmusiło nas do korzystania z Pounda.
Wdrożenie
54
Pierwotnie chcieliśmy aby aplikacja była dostępna przy użyciu HTTPS, co
wydawało się być nietrudnym zadaniem zważywszy na fakt, że Pound potrafi
działać jako front-end tego bezpiecznego protokołu. W trybie tym rozszyfrowywuje
on przesłane do niego żądanie a następnie przesyła je do właściwego serwera
WWW, który albo nie potrafi obsługiwać szyfrowania, albo nie chcemy go obciążać
tym zadaniem. Po wygenerowaniu testowego certyfikatu dla domeny i drobnych
zmianach w pliku:
User "deploy"
Group "users"
ListenHTTPS
Address 0.0.0.0
Port 3030
Cert "/home/rupert/.ssh/server.pem"
Service
BackEnd
Address 127.0.0.1
Port 3040
End
#Analogicznie dla portów 3037-2039
End
End
okazało się jednak, że rodzi to pewne problemy. Ponieważ połączenie pierwotnie
nawiązywane jest z serwerem Apache działającym na maszynie Venema
(pamiętajmy, że Pound chodzi na Stallmanie), do którego konfiguracji nie mamy
dostępu, nie wie on nic o wystawionym dla witryny certyfikacie. Z tego powodu
przeglądarka użytkownika pokazuje bardzo groźny komunikat, że certyfikat dla
odwiedzanej strony (co-do-grosza.pl), jest nieprawidłowy, gdyż został wystawiony
dla innej domeny (na serwerze jest już skonfigurowany certyfikat dla rootnode.pl)
i radzi jej opuszczenie. Użytkownik może w takiej sytuacji mimo wszystko
zaakceptować certyfikat i dodać go do listy wyjątków, a następnie korzystać ze
strony z użyciem szyfrowanego połączenia, jednak w praktyce nikt by się na takie
potencjalne niebezpieczeństwo nie zdecydował. Sprawa byłaby bardzo prosta,
gdybyśmy dysponowali własnym serwerem i mogli na nim ustawić ścieżkę do
certyfikatu. Ostatecznie nie zdecydowaliśmy się na korzystanie z HTTPS.
Mongrel
Z trzech możliwych do wyboru serwerów (mongrel, thin, ebb) wybraliśmy ten
pierwszy, ponieważ jest on najdłużej na rynku i został przez ten czas gruntownie
przetestowany na wielu środowiskach produkcyjnych. Klaster 4 procesów można
uruchomić korzystając z wbudowanego w Rails skryptu spawner do tego:
app> script/process/spawner \
mongrel \
--environment=production \
--instances=4 \
--port=3037
Po uruchomieniu procesów ich numery zostaną zapisane w plikach wewnątrz
katalogu tmp aplikacji. Dzięki temu w późniejszym czasie, istnieje możliość by
korzystając ze skryptu reaper, wykonać jedną z czterech akcji:
Wdrożenie
55
• restart - Powoduje ponowne uruchomienie aplikacji razem ze środowiskiem
Ruby on Rails.
• reload - Przeładowywuje na nowo aplikację, jednak bez całego środowiska RoR.
• graceful - Procesy zostaną zakończone po obsłużeniu aktualnie przetwarzanych
żądań.
• kill - Kończy procesy nawet jeśli są w czasie przetwarzania żądania.
Przykładowo:
app> script/process/reaper -a graceful
Ten sposób uruchamiania i kończenia działania aplikacji w trybie produkcyjnym
nie jest jednak wspierany przez Rails w wersji 2.3. W takim wypadku używa
się polecenia mongrel_rails z odpowiednimi parametrami. Aby z jego pomocą
zarządzać klastrem procesów należy zainstalować odpowiednie gemy w systemie:
mongrel oraz mongrel_cluster.
Capistrano
Capistrano zostało pierwotnie zaprojektowane do zautomatyzowania procesu
wdrażania aplikacji railsowych. Sposobem działania przypomina programy make
oraz rake, w których definiujemy zadania i zależności między nimi a następnie
wykonujemy je w zależności od potrzeb, z tą różnicą, że Capistrano potrafi
uruchamiać zadania na zdalnych komputerach korzystając z protokołu SSH. To
na jakim komputerze zadanie zostanie rozpoczęte, zależy od roli, do której jest
przypisane. Dzięki temu w sytuacji, gdy potrzeby stają się większe i przestaje
się używać jednego serwera do wszystkich zadań (np. w sytuacji postawienia
bazy danych na dedykowanym do tego serwerze), trzeba zmienić tylko plik
konfiguracyjny, by przypisać w nim role dla nowych maszyn.
Po pierwszym uruchomieniu aplikacji na serwerze produkcyjnym, wdrożenie jej
każdej następnej wersji jest bardzo proste i sprowadza się do wydania jednej
komendy (cap deploy:migrations), która [21]:
• pobiera z repozytorium uaktualnioną wersję kodu na serwer aplikacji,
• wykonuje dostępne migracje na bazie danych,
• uaktualnia link wskazujący na katalog z wersją kodu do uruchomienia,
• zatrzymuje i uruchamia serwer aplikacji by zaczął używać najnowszej kopii
oprogramowania.
Capistrano umożliwia podpinanie dodatkowych zadań do wykonania na każdym
z tych etapów (zarówno przed jak i po). Korzystając z tej możliwości rozszerzyliśmy
opisany proces automatycznego wdrażania nowej wersji aplikacji Manage My
Money o:
• pobieranie z innego repozytorium (niedostępnego publicznie) plików
konfiguracyjnych,
• pobieranie dodatkowych rozszerzeń, które pozwalają na monitoring wdrożonej
aplikacji,
Wdrożenie
56
• zarządzanie dodatkowymi usługami potrzebnymi do prawidłowego działania
systemu takimi jak silnik wyszukiwania pełnotekstowego oraz scheduler zadań,
• uruchamianie testów na serwerze aplikacji przed zastosowaniem najnowszego
kodu,
• tworzenie kopi zapasowej bazy danych, indeksów oraz źródeł,
• zamianę niektórych elementów graficznych witryny, w celu wyeksponowania
jej adresu internetowego co-do-grosza.pl .
W przypadku, gdyby uruchomiona wersja była w jakiś sposób wadliwa, istnieje
możliwość szybkiego powrotu do poprzedniej.
57
Rozdział 8
Wnioski
Ruby on Rails to narzędzie pracy, które już od dawna jest w stanie konkurować
z innymi obecnymi na rynku technologiami takimi jak Php, Java, czy .Net
i  dostępnymi na te języki platformami do tworzenia stron internetowych. Nie
stanowi oczywiście rozwiązania wszystkich problemów i w wielu sytuacjach
lepszym wyjściem jest skorzystanie z innego oprogramowania (np. lepiej
wpisującego się w istniejący ekosystem aplikacji klienta).
Przez długi czas słabym punktem Rails był skomplikowany system wdrażania,
jednak od samego początku następuje w tej dziedzinie postęp. Zalecany aktualnie
moduł Phusion Passenger cechuje się wyjątkową prostotą instalacji, konfiguracji
oraz działania, dzięki czemu bariera wejścia obniżyła się jeszcze bardziej.
Początki pracy z Rails mogą jednak nie być łatwe dla niektórych osób, szczególnie
takich, które wcześniej tworzyły oprogramowanie korzystając głównie z systemu
Windows. Większość dokumentacji oraz książek od razu zakłada pewną znajomość
środowiska *nix a przykłady podawane są zwykle tylko dla Mac OS X oraz Linux.
Osoba, która chce się zapoznać z RoR zmuszona jest nie tylko nauczyć się nowego
języka i frameworku ale także kompletnie innego zestawu narzędzi, cechujących
się diametralnie różną filozofią działania niż te znane z Windows. Powoli jednak
następuje coraz większe otwarcie na tą grupę programistów, co naszym zdaniem
jest niezbędne, aby technologia ta zdobyła większe uznanie i popularność biorąc
pod uwagę jak ogromna przepaść dzieli liczbę użytkowników wspomnianych
systemów operacyjnych.
Trudno wyrokować jaka będzie przyszłość Rails i czy zdobędzie sobie na tyle
duże uznanie, by w większej mierze przeniknąć do środowiska korporacyjnego.
Marketingowy sukces jaki osiągnął nie jest jeszcze jednoznaczny z powszechnym
jego używaniem na całym świecie (w Europie przyjmuje się raczej dość wolno,
w Polsce także daleko mu do pozycji lidera, chociaż z Rails skorzystano np.
przy budowie serwisu blip.pl realizowanego przez spółkę GG Network, właściciela
komunikatora Gadu Gadu). Ostatnimi czasy sporo wspomina się w sieci nt. takich
języków jak Erlang, Groovy czy Scala (ma już nawet swój, bardzo przychylnie
recenzowany framework webowy o nazwie Lift), które mają zdetronizować Javę
i  zająć miejsce tego nieco przestarzałego języka [22]. Cały świat ponownie
odkrywa możliwości jakie daje JavaScript i to nie tylko w głównej domenie, jaką
jest osadzanie go na stronach internetowych, ale także do realizowania aplikacji
desktopowych. Nieustannie pojawiają się też języki zupełnie nowe. Jest więc
w czym wybierać.
Skoro dostępnych jest coraz więcej języków o podobnych lub różnych założeniach,
tym co może przesądzić o sukcesie jest możliwość ich integracji z istniejącymi
już na rynku rozwiązaniami. Nie od dziś bowiem wiadomo, że duże systemy
(zatem takie, które najrzadziej są przepisywane z użyciem nowych technologii)
rzadko kiedy działają samodzielnie, raczej w celu realizacji swoich zadań
współpracują z  innymi systemami komputerowymi. Z tego powodu przewiduje
się, że największe szanse będą miały języki wykonywane na platformie JVM,
która uważana jest za bardzo stabilne i dopracowane rozwiązanie i coraz częściej
Wnioski
58
wykorzystuje się ją do uruchamiania kodu, nie napisanego wcale w Javie [22].
Sporą szansą dla Rails jest tutaj projekt JRuby - interpreter języka Ruby napisany
całkowicie w Javie, który umożliwia współpracę obu tych języków i wzajemne
korzystanie z bibliotek. Cały czas rozwija się IronRuby, który miejmy nadzieję już
niedługo, pozwoli korzystać z Ruby'ego, a co za tym idzie z Rails, na platformie
.Net.
Niniejsza praca ogólnie opisuje framework Ruby on Rails oraz w niewielkim stopniu
sam język Ruby. Mamy nadzieję, że już niedługo pojawią się opracowania, których
autorzy podejmą się bardziej szczegółowo opisać wybrane zagadnienia z tej
dziedziny takie jak np. metaprogramowanie oraz optymalizacja.
Jako programiści na co dzień korzystający z ogromnej ilości oprogramowania
open-source, które daje nam pracę i radość, postanowiliśmy udostępnić kod
zrealizowanej przez nas aplikacji na bardzo liberalnej licencji BSD. Osoby chętne
do współpracy lub samodzielnego zainstalowania tego systemu zapraszamy na
stronę: http://github.com/paneq/manage-my-money/.
59
Bibliografia
Książki
[1] Sam Ruby, Dave Thomas, i David Heinemeier Hansson. Copyright © 2009 The
Pragmatic Programmers, LLC. 978-1-9343561-6-6. Agile Web Development
with Rails. Third Edition.
[2] Jarosław Zabiełło. Copyright © 2009 Helion. 978-83-246-0631-3. Ruby on Rails
2.1. Tworzenie nowoczesnych aplikacji internetowych.
[3] Dave Thomas, Chad Fowler, i Andy Hunt. Copyright © 2005 The Pragmatic
Programmers, LLC. Copyright © 2007 Helion (Polish Edition).
978-83-246-0522-4. Programowanie w języku Ruby. Wydanie II.
[4] Kent Beck. Copyright © 2002 Addison-Wesley Professional. 978-0321146533.
Addison-Wesley Professional. Test Driven Development: By Example.
Internet
[5] Scott Ambler. Introduction to Test Driven Design (TDD) [http://www.agiledata.org/
essays/tdd.html].
[6] Robert C. Martin. The Three Laws of TDD [http://butunclebob.com/
ArticleS.UncleBob.TheThreeRulesOfTdd].
[7] Dror Helper. The Cost of Test Driven Development [http://
blog.typemock.com/2009/03/cost-of-test-driven-development.html].
[8] Mike Clark. Test-Driven Development with JUnit Workshop [http://clarkware.com/
courses/TDDWithJUnit.html].
[9] Noel Llopis. Test-Driven Game Development (Part 1) [http://
www.gamesfromwithin.com/articles/0502/000073.html].
[10] Padraic Brady. Designing Klingon Warships Using Behaviour Driven Development
[http://devzone.zend.com/article/3082].
[11] Dan North. Introducing BDD [http://dannorth.net/introducing-bdd].
[12] Test::Unit - Ruby Unit Testing Framework [http://www.ruby-doc.org/stdlib/libdoc/
test/unit/rdoc/classes/Test/Unit.html].
[13] Christoph Olszowka. The Ruby Toolbox: Know your options [http://ruby-
toolbox.com/].
[14] Aslak Hellesøy. Cucumber wiki [http://wiki.github.com/aslakhellesoy/cucumber/
ruby-on-rails].
[15] SeleniumHQ [http://seleniumhq.org/].
Bibliografia
60
[16] D. Robinson i K. Coar. The Common Gateway Interface (CGI) Version 1.1 [http://
www.ietf.org/rfc/rfc3875.txt].
[17] Phusion. Phusion Passenger users guide, Nginx version [http://
www.modrails.com/documentation/Users%20guide%20Nginx.html].
[18] Netcraft. May 2009 Web Server Survey [http://news.netcraft.com/
archives/2009/05/27/may_2009_web_server_survey.html].
[19] Hongli Lai. Passenger and other Ruby frameworks [http://izumi.plan99.net/blog/
index.php/2008/03/27/passenger-and-other-ruby-frameworks/].
[20] Werner Schuster. Phusion Passenger/mod_rails makes Rails deployment easy
[http://www.infoq.com/news/2008/04/phusion-passenger-mod-rails-gc].
[21] Laurie Young. Capistrano Task Graph [http://wildfalcon.com/
archives/2008/09/22/].
[22] Jarosław Zabiełło. Scala - język przyszłości [http://blog.zabiello.com/2009/03/28/
scala-lang].