Krótkie i niekompletne omówienie platformy Ruby on Rails.

Ruby on Rails jest szkieletem aplikacyjnym (ang. framework) do tworzenia aplikacji internetowych działających w oparciu o języki HTML, CSS i JavaScript. Jego autor, David Heinemeier Hansson, został w 2005 roku wybrany został “hakerem roku” na konferencji OSCON (O'Reilly Open Source Conference), właśnie za opracowanie Rails [1]. Logika Rails została napisana w całości w języku Ruby, zaś jako język konfiguracji posłużył YAML. Od czasu wypuszczenia na otwartej licencji w 2004 roku, Ruby on Rails jest intensywnie rozwijany przez powstałą wokół niego społeczność. Aktualna wersja stabilna nosi numer kodowy 2.2. Planowana jest wersja 2.3 oraz 3.0 (która ma być połączeniem Ruby on Rails oraz innego popularnego szkieletu o nazwie Merb). Dokładny opis Ruby on Rails znaleźć można z dokumentacji oraz rewelacyjnych poradnikach zamieszczonych w [2]. Starszą wersję opisuje także książka [3].

Idea

Na rynku oprogramowania jest wiele szkieletów aplikacyjnych służących do tworzenia aplikacji internetowych. Bazują one na rozmaitych ideach. Oto te, które wg autora są szczególnie widoczne w nowoczesnych aplikacjach internetowych.

  • Wykonanie aplikacji w zewnętrznej technologii niezależnej od możliwości przeglądarki w zakresie obsługi HTML, JavaScript i CSS. To podejście umożliwia osiągnięcie jednakowego wygląd aplikacji niezależnie od platformy bez dodatkowego wysiłku, o ile ta jest w stanie obsłużyć wtyczkę dostarczaną przez właściciela technologii, oraz łatwiejsze wykonanie bogatej, multimedialnej aplikacji. Kosztem jest często długi czas ładowania takiej aplikacji oraz konieczność instalacji specjalnej wtyczki przez klienta. Przykładem tego typu podejścia może być Adobe Flash.
  • Upodobnienie tworzenia aplikacji przeglądarkowej do sposobu tworzenia aplikacji biurkowej - symulacja zdarzeń dla poszczególnych kontrolek (np. „po kliknięciu”), obudowanie kontrolek HTML własnymi, wysokopoziomowymi klasami, przypominającymi te z standardowych, „biurkowych” interfejsów użytkownika. W najbardziej skrajnym przypadku programista korzystający z tej technologii może wykonać aplikację działającą w oparciu o technologie HTML, CSS i JavaScript nie wiedząc nic o tych językach. Przykładem tego typu podejścia jest ASP.NET (szczególnie wspomagane przez narzędzie Visual Studio).
  • Przeniesienie tworzenia warstwy widoku na wyższy poziom abstrakcji, poprzez zapewnienie konfigurowalnych znaczników oraz ujednolicenie kodu widoku wykonywanego po stronie serwera z kodem interpretowanym przez klienta. Przykładem tego podejścia są strony JSP (Java Server Pages) oraz ASP.NET.
  • Utworzenie szeregu skrótów, opakowań i rozsądnych wartości domyślnych, które pozwalają pisać na podobnym poziomie abstrakcji, ale wydajniej. np. utworzenie własnego DDL, opakowującego zależny od bazy danych dialekt SQL, opakowanie na biblioteki JavaScript etc. Przykładem takiego podejścia jest właśnie Ruby on Rails, ale także inne szkielety aplikacyjne, takie jak Grails, DJango, czy też CakePHP.

Ruby on Rails nie ma na celu uwolnienia programisty od konieczności dobrej znajomości zagadnień związanych z językami przeglądarek internetowych (HTML, JavaScript, CSS), lecz ułatwienie automatyzacji szczególnie uciążliwych lub monotonnych fragmentów pracy z tymi językami.

Cechy

Ruby on Rails zawiera wiele cech, które mają za zadanie uczynić korzystającego z niego programistę bardziej produktywnym. Bruce A. Tate i Curt Hibbs w [4] wymieniają następujące najistotniejsze cechy szkieletu Ruby on Rails:

  • Metaprogramowanie - większość mechanizmów Ruby on Rails wykorzystuje możliwości metaprogramowania jakie daje język Ruby i pozwala w ten sam sposób je modyfikować (idea metaprogramowania w Ruby opisana została w sekcji {MetaprogrammingRubySection}).
  • Szkielet mapowania obiektowo - relacyjnego - Ruby on Rails zawiera gotową do użycia i zintegrowaną z innymi komponentami bibliotekę służącą do opakowania rekordów bazy danych obiektami aplikacji. Biblioteka ta zapewnia automatyczne wykrywanie zależności pomiędzy polami klasy opakowującej, a kolumnami bazy danych, korzystając z technik metaprogramowania. Szersze omówienie implementacji mapowania obiektowo - relacyjnego w Ruby on Rails zawarte jest w sekcji {RubyOnRailsModel}.
  • Konwencja ponad konfiguracją - termin ten stał się modny od czasu wydania szkieletu Rails, który wymaga minimum konfiguracji, w zamian za przestrzeganie pewnych konwencji nazewniczych. Przy odpowiednim doborze nazw tabel w bazie danych, kluczy obcych czy też struktury katalogów Rails jest w stanie zautomatyzować wiele procesów takich jak np. mapowanie tabel na obiekty, czy też odnajdowanie szablonów stron HTML. Pozwala to znacznie zredukować rozmiar kodu konfiguracyjnego (wg [4] - nawet do pięciu lub więcej razy).
  • Automatyzacja - w szkielecie Rails zapewniona jest przez zestaw generatorów, które potrafią wygenerować szablony najczęściej używanych konstrukcji, takich jak kontrolery, klasy modelu opakowujące relacje bazodanowe, czy też migracje schematu. Utworzenie nowej aplikacji opartej o Rails również polega na wywołaniu generatora, który tworzy domyślną strukturę plików i katalogów. Dodatkowe wtyczki często instalują swoje własne generatory, które dodatkowo rozszerzają możliwości automatyzacji. Innym pomocnym udogodnieniem są zadania dla narzędzia Rake (omówione w sekcji {RubyRake}).
  • Wbudowany mechanizm testowania kodu, przystosowany specjalnie do testowania aplikacji napisanych w oparciu o Rails.
  • Wbudowana obsługa dla trzech oddzielnych środowisk - rozwojowego, testowego oraz produkcyjnego. Każde z tych trzech środowisk posiada swoją własną bazę danych (mogą to być bazy działające w oparciu o inne systemy zarządzania bazami danych, np. baza dla środowiska testowego może używać SQLite, natomiast baza dla środowiska produkcyjnego - PostgreSQL), oraz pewną domyślną konfigurację która sprawia, że dane środowisko zachowuje się w specyficzny sposób się specyficznie do przeznaczenia, np. w środowisku testowym baza danych jest przeznaczona do testów i po uruchomieniu ich, jest czyszczona i odtwarzana z bazy dla środowiska rozwojowego za każdym razem, gdy jest używana.

Architektura Ruby on Rails

Szkielet Ruby on Rails stosuje wzorzec architektoniczny model - widok - kontroler (ang.
model - view - controller, stosowany jest skrót MVC). Wzorzec ten, wg [4],
zakłada podział aplikacji na trzy ortogonalne warstwy:

  • Model – odpowiedzialny za logikę domeny.
  • Widok – odpowiedzialny za prezentację wyników działania użytkownikowi oraz przyjmowania od niego komend.
  • Kontroler - cienka warstwa pomiędzy modelem, a widokiem, odpowiedzialna za zapewnienie komunikacji między warstwą widoku i modelu (m.in. przetwarzanie komend użytkownika i przekazywanie do widoków obiektów domenowych).

Dzięki takiemu podziałowi aplikacja napisana w oparciu o MVC jest bardziej modularna i elastyczna - jako że logika domenowa oddzielona jest od logiki prezentacji, wymiana funkcjonalności realizującej daną logikę jest możliwe bez modyfikacji (albo z niewielkimi modyfikacjami) pozostałej części logiki.

Pierwotna wersja wzorca MVC powstała w połowie lat siedemdziesiątych na potrzeby programistów języka Smalltalk i służyła tworzeniu interfejsów użytkownika dla aplikacji biurkowych. Przeniesienie jej w bezpośredni sposób na pole aplikacji opartych na zastosowaniu protokołu HTTP nie było możliwe,
więc dokonano pewnych modyfikacji (np. mechanizm prenumeracji - powiadamiania, czyli natychmiastowe altualizacji widoku po zmianie modelu, nie jest stosowany w przypadku bezstanowych aplikacji przeglądarkowych). Tę zmodyfikowaną formę wzorca MVC, przystosowaną dla aplikacji WWW nazwano Model2 (wg [4]). Sama nazwa wywodzi się z środowiska Java EE, gdzie mianem Model1 określano architekturę, w której servlet, lub strona JSP, która otrzymała żądanie, zajmowała się całością przetwarzania i generowania odpowiedzi, natomiast jej rozwinięcie, używające wzorca MVC (zaimplementowane np. w szkielecie Struts) otrzymało nazwę Model2.

Poniżej rysunek, zaczerpnięty z [4] przedstawia połączenia pomiędzy elementami architektury Ruby on Rails.

RailsRequestProcessing1.png

Analizując przetwarzanie żądania HTTP przez aplikację opartą o Ruby on Rails można zauważyć, jak poszczególne elementy architektury współpracują ze sobą. Proces ten można rozbić na następujące etapy:

  • Klient (np. przeglądarka internetowa) wysyła żądanie do serwera, na którym znajduje się aplikacja.
  • Serwer WWW przekazuje żądanie do dyspozytora, który decyduje, jakiego typu kontrolera użyć oraz którą metodę tego kontrolera wykorzystać do obsługi żądania. Następnie wywołuje tę metodę, przekazując jej parametry żądania.
  • Metoda kontrolera pobiera parametry żądania i w oparciu o nie wykonuje swoją logikę, sprowadzającą się zazwyczaj do pobrania odpowiednich obiektów domeny z warstwy modelu.
  • Po przygotowaniu odpowiednich danych na podstawie szablonu warstwy widoku oraz obiektów domeny generowana jest odpowiedź.
  • Odpowiedź zostaje przekazana do klienta.

W następnych sekcjach przedstawione zostaną szczegółowo warstwy wchodzące w skład Ruby on Rails.

Model

Za obsługiwanie warstwy modelu w szkielecie Ruby on Rails domyślnie odpowiedzialne są biblioteki ActiveRecord (opakowująca dostęp do bazy danych) oraz ActiveResource (opakowująca dostęp do zasobów sieciowych). Istnieją również inne biblioteki twórców zewnętrznych, którymi można zastąpić te
domyślne. W tej sekcji zostanie omówiona jedynie biblioteka ActiveRecord, gdyż tylko z nią miałem do czynienia, natomiast istnieją także inne, np. DataMapper, czy też Sequel.

Biblioteka ActiveRecord bazuje na wzorcu projektowym aktywnego rekordu, zaproponowanym przez Martina Fowlera w [5]. Wg ww. pozycji, aktywny rekord, to obiekt, który opakowuje pojedynczą krotkę w tabeli bazy danych, zamyka w sobie dostęp do tej bazy oraz dodaje
logikę domenową do tych danych. Ruby on Rails implementuje tę kwestię w ten sposób, że każda klasa opakowuje dostęp do tabeli, natomiast obiekt tej klasy - do krotki tej tabeli.

Samą bibliotekę można podzielić na dwa mechanizmy - system migracji (opisany dalej) oraz klasy opakowujące. Przykład takiej klasy ilustruje zamieszczony poniżej listing, na którym widać, że, w odróżnieniu od innych szkieletów mapowania obiektowo relacyjnego (np. Hibernate), klasa ActiveRecord nie posiada pól, na które mapowane są pola z tabeli. Dzieje się tak dlatego, że klasa taka sama jest w stanie pobrać z bazy danych odpowiednie nazwy i wartości pól, bazując na konwencjach nazewniczych.

class Invitation < ActiveRecord::Base
  # Asocjacje:
  belongs_to :participant
  belongs_to :invitation_status
  belongs_to :presence_priority
  belongs_to :event
 
  # Walidacje:
  validates_presence_of :participant
  validates_presence_of :event
  validates_presence_of :invitation_status
  validates_presence_of :presence_priority
 
  # Logika domeny:
  def invited_participant_login
    self.participant.login
  end
 
  def self.find_all_tentative_for(attendee)
    return find(
      :all,
      :conditions => 
        ["participant_id = ? and invitation_status_id = ?",
          attendee.id,
          InvitationStatus.tentative])
  end
end

Na poniższym listingu wyróżnić można trzy rodzaje elementów, w które wyposażona jest zazwyczaj klasa ActiveRecord.

Asocjacje

Asocjacje definiowane są w modelach ActiveRecord w następujących celach:

  • W celu ustalenia zależności między krotkami danej tabeli, a krotkami innych tabel, w celu późniejszego odpowiedniego wiązania obiektów domenowych opakowujących te krotki.
  • W celu ustalenia charakteru tej zależności (jeden-do-jednego, jeden-do-wielu, wiele-do-wielu),
  • W celu zapewnienia spójności pomiędzy tymi krotkami (np. usunięcie danego rekordu powoduje kaskadowe usunięcie wszystkich zależnych rekordów).

Jako że aplikacje bazodanowe najczęściej są klientami bazy danych (w sensie architektury klient-serwer), różne są koncepcje podziału odpowiedzialności pomiędzy te dwie warstwy.

Koncepcja cienkiego klienta

Koncepcja ta zakłada, że większość logiki odpowiedzialnej za przetwarzanie danych powinna znaleźć się w bazie danych, natomiast aplikacja kliencka powinna tylko przekazywać parametry zapytań, pobierać wyniki oraz prezentować je użytkownikowi. Koncepcja ta opiera się na intensywnym użyciu takich mechanizmów bazodanowych, jak procedury składowane, wyzwalacze, wiązanie kluczy obcych, więzy integralności itp.

Koncepcja ta sprawdza się przede wszystkim wtedy, gdy wiele różnych aplikacji klienckich korzysta z tego samego zbioru danych - wtedy zmniejsza ryzyko wystąpienia niespójności pomiędzy danymi, które mogą spowodować niewłaściwe zachowanie się innych aplikacji. Dodatkową zaletą takiego podejścia jest zmniejszona konieczność powtórnej implementacji tej samej logiki w wielu aplikacjach.

Koncepcja grubego klienta.

Koncepcja ta zakłada, że baza danych powinna służyć tylko i wyłącznie do przechowywania danych oraz udostępniania ich na żądanie. Wg tej koncepcji, baza powinna posiadać minimalną wiedzę o tym, do czego są używane i jak uzależnione od siebie są dane składowane w poszczególnych tabelach. Cała wiedza na temat logiki domenowej i logiki przetwarzania informacji znajduje się w aplikacji napisanej najczęściej w języku trzeciej generacji.

Koncepcja ta sprawdza się zwłaszcza wtedy, gdy występuje jedna aplikacja, która musi współpracować z wieloma różnymi silnikami bazodanowymi. W takim przypadku zmiana bazy danych pociąga za sobą minimalną konieczność przepisywania kodu (np. z jednego dialektu języka SQL na inny).

Biblioteka ActiveRecord stanowi implementację koncepcji grubego klienta - wszystkie więzy integralności, asocjacje między krotkami itp. definiowane są w warstwie modelu w klasach obiektów domenowych.

  • Asocjacje - konfiguracja powiązań krotek tabeli z krotkami innej tabeli. Warto zauważyć, że ActiveRecord nie bazuje na asocjacjach definiowanych na poziomie bazy danych (tzw. więzach integralności).
  • Walidacje - ograniczenia nakładane na wartości przypisywane do danych pól klasy. Jeśli przypisywane wartości nie będą spełniały nałożonych ograniczeń, rekord nie zostanie zapisany w tabeli,
  • Logika domeny - dodatkowe metody mające na celu ułatwienie aplikacji dostępu do interesujących ją danych bez konieczności znajomości szczegółów modelu danych.

Walidacje

ActiveRecord umożliwia dodanie do klas własnych walidacji, jak również skorzystanie z predefiniowanych i podajnie jedynie, dla których pól obiektu mają obowiązywać. Jeśli taka walidacja nie zostanie przeprowadzona pomyślnie, obiekt nie zostanie zapisany do bazy danych oraz zostanie wypełniona specjalna wewnętrzna tablica z opisami błędów, którą można następnie wyświetlić użytkownikowi. W ten sposób zamyka się logikę walidacji danych w samym obiekcie.

Następujący listing pokazuje przykłady kilku walidacji możliwych do przeprowadzenia w ActiveRecord:

class Person < ActiveRecord::Base
 
# sprawdzenie unikatowości
validates_uniqueness_of :pesel
 
# sprawdzenie wypełnienia pola
validates_presence_of :name
validates_presence_of :surname
 
# sprawdzenie formatu
validates_numericality_of :age
 
#sprawdzenie zakresu pola
validates_length_of :login, 
     :within => 6..20, 
     :too_short => "login musi zawierac co najmniej 6 znakow"
     :too_long => "login nie moze byc dluzszy niz 20 znakow",
 
# wyspecjalizowana walidacja (tylko podczas utworzenia)
def validate_on_create
  if(self.age < 18 )
    errors.add(:age, " to low to get an account in this bank." )
  end
end

Logika domeny

Logiką domenową mogą być dowolne metody istotne z punktu widzenia logiki aplikacji. Najczęściej są to specjalne metody dostępu do danych, które zwalniają warstwy kontrolera i widoku z konieczności znajomości schematu bazy danych i nazw poszczególnych pól. Najprostszym przykładem mogłaby być metoda „znajdź pełnoletnich Polaków”, która zadawałaby pytanie o wszystkie osoby, dla których pole „narodowość” ma zawartość „PL”, a pole „wiek” - wartość większą niż „18”.

Widok

Warstwa widoku w Ruby on Rails posługuje się, podobnie jak np. w PHP, czy też JSP, szablonami statycznego kodu (może to być HTML, XML czy też inny format), uzupełniamymi wstawkami kodu wykonywanego po stronie serwera. Kod ten może służyć do dynamicznego generowania fragmentów dokumentu zależnych od stanu aplikacji, bądź też wykonywania dowolnej innej logiki. Po wykonaniu takiego kodu, rezultat przesyłany jest do klienta.

Domyślnie każdy widok przetwarzany jest przez przyporządkowany mu interpreter. Rails wnioskuje, który interpreter ma zostać użyty, na podstawie nazwy pliku, np. plik new.html.erb zostanie przetworzony przez silnik ERB (opisany w następnej sekcji), natomiast plik new.html.haml - przez interpreter HAML (opisany dalej). Interpretery można podłączać do aplikacji opartej o Ruby on Rails na zasadzie wtyczek. Opisane zostaną trzy interpretery - ERB, HAML i RJS.

ERB

ERB (Embedded Ruby) jest domyślnym interpreterem dla szablonów stron HTML. Jego koncepcja opiera się podobnym pomyśle, jak w przypadku stron PHP, ASP, czy też JSP. Pomiędzy odpowiednimi znacznikami umieszczany jest kod języka Ruby, który, w zależności od rodzaju znacznika, albo zostaje wykonany nie pozostawiając po sobie śladu na stronie, albo też jego wynik zostaje bezpośrednio włączony do kodu przesłanego klientowi. Interpreter ERB wyszukuje te specjalne znaczniki i interpretuje tylko to, co znajduje się pomiędzy nimi, pozostawiając resztę dokumentu bez zmian. Przykład wykorzystania ERB podczas generowania strony przezdstawia poniższy listing.

<h1>New client</h1>
<%= error_messages_for :client %>
<% form_for(@client) do |f| %>
  <p>
    <b>Name</b><br />
    <%= f.text_field :cilent_name %>
  </p>
 
  <p>
    <b>Description</b><br />
    <%= f.text_field :client_description %>
  </p>
 
  <p>
    <%= f.submit "Create" %>
  </p>
<% end %>
 
<%= link_to 'Back', clients_path %>

ERB jest tylko jedną z implementacji idei kodu Ruby osadzonego w kodzie HTML. Istnieją inne, szybsze rozwiązania, takie jak Erubis [6], który, w niektórych przypadkach, oferuje osiągi prawie trzy razy lepsze od ERB.

HAML

HAML został opracowany z myślą o ułatwieniu pisania dynamicznych stron HTML. Jego cele to:

  • Przyspieszenie pisania kodu HTML i osadzania w nim kodu Ruby poprzez zredukowanie do minimum udziały znaków wynikających tylko z gramatyki języka w pisanym kodzie. W tym celu uproszczono najczęściej używane znaczniki i najczęściej używane właściwości do pojedynczych znaków. Zamieszczony poniżej listing przedstawia przykład takiego uproszczenia.
<!-- HTML: -->
<div id="identyfikator" class="klasa"><%="tekst".t%></div>    

/HAML:
#identyfikator.klasa="tekst".t
  • Zagwarantowanie, że każdy znacznik HTML zostanie zamknięty - uwolnienie twórcy stron od konieczności pamiętania (a zatem również i możliwości zapomnienia) o zamykaniu każdego otwartego znacznika. Ten cel został osiągnięty w podobny sposób, co minimalizacja składni bloków kodu w języku Python - poprzez zwiększenie znaczenia białych znaków w składni języka. Innymi słowy, wszystko, co jest odpowiednio przesunięte względem znacznika, znajduje się w jego środku. Znacznik jest zamykany automatycznie po napotkaniu na element o takim samym stopniu wcięcia wiersza, lub napotkania końca dokumentu. Ilustruje to następujący listing:
%table            => <table>
  %tr             =>   <tr>
    %td="tekst1"  =>     <td>tekst1</td>
    %td="tekst2"  =>     <td>tekst2</td>
    %td="tekst3"  =>     <td>tekst3</td>
                  =>   </tr>
  %tr             =>   <tr>
    %td="tekst4"  =>     <td>tekst4</td>
    %td="tekst5"  =>     <td>tekst5</td>
    %td="tekst6"  =>     <td>tekst6</td>
                  =>   </tr>
                  => </table>
  • Zaadresowanie częstej bolączki twórców stron, jaką jest brak formatowania generowanego kodu. Omawiany powyżej listing przedstawia kod HAML i wygenerowany z niego kod HTML, który, w przeciwieństwie do wielu wygenerowanych w inny sposób fragmentów kodu, jest czytelnie sformatowany.

Wadą interpretera HAML jest jego szybkość. W przeciwieństwie do interpretera ERB i jemu podobnych, HAML (z uwagi na wykorzystywanie specjalnego języka do budowy całej strony, a nie tylko dynamicznych wstawek) przetwarza cały dokument od początku do końca, generując nie tylko zmienne, ale także i statyczne części strony.

RJS

Pliki z rozszerzeniem .js.rjs traktowane są przez Ruby on Rails jako pliki tzw. zdalnego JavaScript (ang. Remote JavaScript). Pliki takie służą do generowania czystego kodu JavaScript, który następnie jest zwracany do klienta. Ma to zastosowanie przede wszystkim w odpowiedzi na żądanie AJAX, gdyż taki zwrócony kod JavaScript jest od razu wykonywany przez przeglądarkę intetrnetową, umożliwiając uzyskanie zaawansowanych dynamicznych efektów na stronie.

Pliki RJS nie mają specjalnego interpretera. Pisane są w języku Ruby przy użyciu specjalnej biblioteki, opakowującej bibliotekę JavaScript o nazwie Script.aculo.us [7], a nastepnie wykonywane przez interpreter języka Ruby. W końcu rezultat wykonania przesyłany jest do klienta (lub ewentualnie do zmiennej, którą można wykorzystać później).

Przykładowy fragment kodu RJS:

# Umieszczenie napisu w elemencie o id="conference_messages":
page['conference_messages'].value = @messages_string
 
# Zmiana położenia suwaka okna o id="conference_messages":
page['conference_messages'].scrollTop = @messages_string.length
 
# Usunięcie zawartości elementu o id="message_content":
page['message_content'].value = ""

Klasy i metody pomocnicze

Ruby on Rails udostępnia szereg klas i metod pomocniczych, mających na celu uczynienie przygotowywania widoków prostszym.

Odnośniki.

Ruby on Rails opiera się na koncepcji zasobów. Każdy zasób ma swoją własną klasę obiektów domenowych oraz zestaw stron. Dzięki temu założeniu możliwe jest wykorzystanie takich obiektów domenowych podczas generowania odnośników do stron. Uzyskuje się to poprzez podanie obiektu domenowego jako argumentu metody pomocniczej link_to. Ilustruje to następujący fragment kodu:

  # Wygenerowanie odnośnika poprzez 
  # podanie obiektu domenowego @account
  # o polu id równym 2:
  = link_to 'Show'.t, @account 
  # Wynik: <a href="http://app.com/accounts/2">Show</a>

Budowniczy formularzy.

Koncepcja formularzy w Ruby on Rails opiera się o wzorzec projektowy Budowniczy, opisany w [8]. Biblioteka ActionPack wchodząca w skład Ruby on Rails udostępnia metodę tworzącą formularz, która udostępnia obiekt budowniczego wewnątrz otwartego przez siebie bloku. Wewnątrz bloku zawarte są polecenia dla budowniczego, które wykonuje w momencie zamknięcia bloku. Przykład takiego działania:

/ Otwarcie bloku i udostępnienie budowniczego:
-form_for(@issue) do |budowniczy| 

  /Polecenie dla budowniczego:
  =budowniczy.label(:issue_name, "Issue name".t)

  /Polecenie dla budowniczego:
  =budowniczy.text_field :issue_name 

  /Polecenie dla budowniczego:
  =budowniczy.submit "Create".t
/ Zamknięcie formularza - wykonanie wszystkich poleceń

Dzięki takiemu rozwiązaniu kwestii formularzy, można napisać własną klasę budowniczego, która sprawi, że wszystkie formularze w aplikacji będą przestrzegały tych samych reguł formatowania, oraz uwolni programistę od konieczności formatowania każdego nowego formularza.

Następny listing przedstawia przykład wykorzystania własnego budowniczego formularzy:

-form_for([@project, @milestone], :builder => DefaultFormBuilder) do |f|

  = f.label(:codename, 'Codename'.t)
  = f.text_field :codename

  = f.label(:description, 'Description'.t)
  = f.text_area :description

  = f.label(:deadline, 'Deadline'.t)
  = calendar_date_select_block_tag('milestone[deadline]', @milestone.deadline, :popup => true)

= f.submit 'Create'.t

Wynik przetworzenia tego kodu przedstawia następujący rysunek:

RailsFormBuilderOutcome.png

Metody pomocnicze dla AJAX.

W celu integracji mechanizmów AJAX, Ruby on Rails korzysta z napisanych w języku JavaScript bibliotek Prototype i script.aculo.us. z poziomu języka Ruby zapewnione są biblioteki opakowjące, za pomocą których można bezpośrednio w Rubym pisać kod JavaScript. Poniższy Listing przedstawia przykładowy kod widoku, który będzie reagować na zmiany w polach formularza, odpytywać serwer i w zależności od odpowiedzi aktualizować odpowiedni element.

#validation_result.validation_result
  %p="You can test your content against validation pattern in the text field below".t

-form_for(:contact_type, @contact_type, 
        :url => {:action => :apply_validation_pattern}, 
        :builder => DefaultFormBuilder, 
        :html => {:id => 'contact_type_validation_form'}) do |f|

  =f.hidden_field(:validation_pattern)

  =fields_for(:check, :builder => DefaultFormBuilder) do |fields|
    =fields.text_area(:content, :rows => 3)

/ Obserwuj formularz 
= observe_form(:contact_type_validation_form, 
        :frequency => 1, 
        :url => {:action => :apply_validation_pattern}, 
        :update => :validation_result)

Efekt wykonania tego kodu:

RailsAjaxExample.png

Widoki częściowe.

Ruby on Rails umożliwia składanie stron z fragmentów. Idea polega na wydzieleniu ze strony fragmentu i utworzenie z niego osobnego fragmentu kodu, który potem osadzany jest w stronie oryginalnej. Idea ta jest także znana na platformie JEE, pod postacią biblioteki Apache Tiles. W Ruby on Rails mechanizm ten występuje pod nazwą widoków częściowych (ang. partials).

Podejście oparte na widokach częściowych pozwala m.in. na uzyskanie modułów wielokrotnego użytku, które można osadzać w więcej niż jednej stronie bez konieczności duplikacji kodu źródłowego. Inną korzyścią jest możliwość wykorzystania widoku częściowego w odpowiedzi na żądanie AJAX. Takie użycie widoku częściowego przedstawia poniższy listing:

%h1.section_header
="Info".t

/ Odnośnik do otwarcia sekcji:
=link_to_remote_partial("Show".t, "info_section_id", "info", @participant)

/ Odnośnik do ukrycia sekcji:
=link_to_remote( "Hide".t, :update => "info_section_id", :url => {:action => :render_nothing, :partial => "info"} )

/ Element aktualizowany widokiem częściowym:
#info_section_id=show_partial_if_session_param(:partial => "info", :instance => @participant)

Następujący rysunek przedstawia efekty wykonania tego kodu przed kliknięciem na odnośnik powiązany z akcją wyświetlającą widok częściowy:

RailsPartialsExampleBefore.png

Po kliknięciu na odnośnik następuje odwołanie do serwera, który zwraca zawartość częściowego widoku, którą następnie aktualizowany jest element sekcji (co pokazane zostało na poniższym rysunku).

RailsPartialsExampleAfter.png

Kontroler

Kontrolery w Ruby on Rails zajmują się obsługą żądania, najczęściej poprzez przygotowanie obiektów domenowych, wyrenderowanie widoku i wysłania go do klienta. Same kontrolery to klasy, które dziedziczą po wspólnej nadklasie kotrolera aplikacji. Każdy kontroler zawiera szereg metod, zwanych akcjami, które zajmują się obsługą żądań. Wybranie odpowiedniej akcji odbywa się za pomocą konwencji nazewniczej oraz metody protokołu HTTP użytej do wysłania żądania.

Rails implementuje koncepcję REST (Representational State Transfer opisaną np. w [9]). Metody protokołu HTTP mapowane są na akcje CRUD (pierwsze litery od słów Create, Read, Update, Destroy - Utwórz, Odczytaj, Zmień, Zniszcz). Możliwe jest zatem wywołanie akcji kontrolera poprzez podanie jego URL oraz użycia odpowiedniej metody HTTP. Niżej zamieszczona tabela przedstawia domyślne mapowania metod protokołu HTTP na akcje kontrolera.

Najczęściej klientem aplikacji opartej o Ruby on Rails jest przeglądarka internetowa. Przeglądarki internetowe najczęściej implementują tylko dwie metody HTTP: GET i POST. Dlatego też Ruby on Rails przewiduje specjalne obejścia dla tego typu klientów [9].

Metoda HTTP Akcja kontrolera Uwagi
POST create -
PUT update -
GET show w zależności od URL
GET index w zależności od URL
DELETE delete -

Drugą istotną cechą kontrolerów Ruby on Rails jest możliwość reagowania na ządany format zasobu. W ten sposób można stosunkowo łatwo zaimplementować mechanizm web-services w oparciu o istniejące już akcje kontrolerów - jedynie dostosowując je do zwracania klientowi kodu XML. Przykład takiego wykorzystania akcji kontrolera:

def show
  @participant = Participant.find(params[:id])
  @attendee = @participant
  @invitations = Invitation.find_all_tentative_for(@attendee)
  @projects = @participant.current_projects  
 
  init_dates_input_by_user(BEGIN_DATE_KEY, END_DATE_KEY)
 
  define_actions_for_show
 
  # Zróżnicowanie zachowania w zależności od formatu:
  respond_to do |format|
    # Żądanie o dokument HTML:
    format.html 
    # Żądanie o dokument XML:
    format.xml  { render :xml => @participant }
  end
end

Bibliography
3. D. Heinemeier Hansson, Agile. Programowanie w Rails. Wydanie II, helion 2008
4. B. A. Tate, C. Hibbs, Ruby on Rails. Wprowadzenie, helion 2007
8. E. Gamma, R. Helm, R. Johnson, J. Vlissides, Wzorce projektowe. Elementy oprogramowania obiektowego wielokrotnego użytku
9. R. Wirdemann, T. Baustert, RESTful Rails Development, http://www.b-simple.de/download/restful_rails_en.pdf
O ile nie zaznaczono inaczej, treść tej strony objęta jest licencją Creative Commons Attribution-Share Alike 2.5 License.