Programowanie Ruby/RubyOnRails Serwis zdjęć z wakacji Marek Sawerwain Tworzenie rozbudowanych serwisów internetowych to trudne zadanie. Co więcej, staje się ono bardzo trudne, jeśli wszystkie elementy będą tworzone od podstaw. Ten problem został bardzo szybko rozwiązany i powstały tzw. frameworki, czyli gotowe systemy, które wspomagają pracę nad tworzeniem rozbudowanych serwisów internetowych. ista obecnie dostępnych systemów jest bardzo Przykład na rozgrzewkę duża. Mniej popularne rozwiązania to systemy Nasz pierwszy przykład sprowadzi się do opracowania takie jak np. Yaws (napisany w języku Erlang), aplikacji, która generuje prosty komunikat tekstowy. Jed- Lczy Seaside opracowany no Smalltaku. Do naj- nakże, aby nieco skomplikować zadanie, zrobimy to na bardziej popularnych należą np. Zope, Zend, Apache Coco- trzy sposoby. Zakładamy, że będziemy pracować z pozio- on oraz pakiet RubyOnRails, który w ostatnim czasie zyskał mu konsoli - choć trzeba nadmienić, że istnieje specjal- bardzo wiele na popularności. na odmiana środowiska Eclipse o nazwie Aptana wraz z RubyOnRails zdobywa coraz większą popularność rozszerzeniem do tworzenia aplikacji Rails. Niestety, jego dzięki świetnie przemyślanej budowie. Dla wielu typowych wadą jest spore zapotrzebowanie na pamięć. 1GB pamię- zadań, jakie się napotyka podczas tworzenia serwisów (np. ci RAM jest niezbędny do w miarę sprawnej pracy. Na- współpraca z bazą danych) RubyOnRails oferuje gotowe tomiast zaletą jest spora wygoda, ponieważ Aptana ofe- rozwiązania, co w efekcie przekłada się na przyspieszenie ruje wszystkie elementy do tworzenia serwisów, od wła- pracy nad serwisem. Jednakże, każde narzędzie warto po- snej implementacji języka Ruby, poprzez niezbędne ser- znać podczas realizacji choćby najprostszego projektu. Dla- wery; oferuje także wbudowany serwer bazy danych oraz tego warto spróbować zrealizować prosty projekt, np. ser- przeglądarkę. wis, w którym to użytkownicy będą mogli umieszczać zdję- Pierwsza czynność to utworzenie szkieletu aplikacji. cia np. z wakacji. W dowolnym katalogu, nawet w katalogu domowym, z Nasz projekt opracujemy w wersji 1.2.6 pakietu RubyOn- poziomu konsoli wydajemy polecenie rails App1. Na- Rails. Istnieje nowa wersja 2.0 ale jak na razie najbardziej po- zwa naszej aplikacji to App1 możemy naturalnie podać pularna jest wersja 1.2.x. Tym bardziej, iż większość dostęp- inną. Wynikiem działania wydanego polecenia jest katalog nych książek oraz informacji jakie znajdziemy w Internecie zawierający kompletny szkielet naszej aplikacji. Możemy powołuje się na nieco starszą wersję pakietu RubyOnRails. nawet już w tym momencie uruchomić serwer, przecho- 50 lipiec/sierpień 2008 linux@software.com.pl Programowanie Ruby/RubyOnRails dząc do katalogu i wydając odpowiednie po- pomocą kombinacji klawiszy CTRL-C. Po uru- niem będzie teraz dopisanie metody o nazwie lecenie, co przedstawia się następująco: chomieniu serwera, co może być dla wielu osób index, która będzie domyślnie wywoływana zaskoczeniem, możemy zacząć tworzyć naszą w aplikacji po wpisaniu do przeglądarki ad- cd App1 pierwszą aplikację. W oddzielnym oknie konsoli resu http://127.0.0.1:3000/newindex. Metoda ruby script/server ponowie przechodzimy do katalogu naszej apli- index przedstawia się bardzo prosto: wyko- kacji i wydajemy następujące polecenie: rzystując akcję render do pola text wpisuje- Jeśli instalacja języka Ruby oraz pakietu Rails my treść naszego komunikatu (Listing 1.) odbyła się w sposób podobny do opisanego w ruby script/generate controller I jest to pierwszy sposób w jaki możemy ramce, to aplikacja została uruchomiona za po- newindex wyświetlić komunikat. Drugim sposobem jest mocą serwera Mongrel i jest dostępna lokalnie umieszczenie komunikatu w pliku html i wyświe- pod adresem http://127.0.0.1:3000. Serwer uru- Zostanie utworzony obiekt odpowiedzialny za tlenie jego zawartości w następujący sposób: chomiony z poziomu konsoli łatwo wyłączyć za kontroler o nazwie newindex. Naszym zada- render :file => ścieżka/do/pliku,html Ostatnim sposobem jest utworzenie w meto- dzie index zmiennej o nazwie np. message- ToTheWorld w następujący sposób: def index @messageToTheWorld = treść naszej wiadomości end Listing 1. Metoda index odpowiedzialna za wyświetlenie komunikatu class NewindexController < ApplicationController def index render :text => a tu znajduje się bardzo śmieszna Rysunek 1. Wynik działania pierwszej aplikacji oraz domyślna strona serwera wiadomość end end serwer bazy danych My SQL Listing 2. Polecenie SQL tworzące tabelę ze zdjęciami create table photos ( id int primary key auto_ edycja zdjęcia nowe zdjęcie increment, lista zdjęć usunięcie pokaż zdjęcie picture mediumblob, zdjęcia description text, name text ); kontroler aplikacji photos Listing 3. Przykładowa zawartość pliku data- base.yml pozbawiona sekcji test development: adapter: mysql database: photodb serwer aplikacji Mongrel url: localhost username: root password: root production: adapter: jdbc przegl darka WWW driver: org.apache.derby.jdbc.C lientDriver url: jdbc:derby://localhost/ RubyTest1_production;create=true użytkownik username: app serwisu password: app Rysunek 2. Schemat funkcjonowania naszego serwisu ze zdjęciami www.lpmagazine.org 51 Programowanie Ruby/RubyOnRails Wywołanie akcji index spowoduje utworzenie Fragment kodu objętego tagami <% oraz %>. Listing 4. Fragmenty klasy kontrolera zmiennej, ale aby wyświetlić jej zawartość na- To naturalnie program w języku Ruby. Jest leży utworzyć dodatkowy plik który zajmie się to analogiczne rozwiązane jak w przypad- class PhotosController < wyświetleniem zawartości zmiennej. Dodat- ku PHP. ApplicationController kowy plik o nazwie index.rhtml tworzymy w W podanym przykładzie podajemy tylko podkatalogu app\views\newindex. Ostatni ele- nazwę zmiennej, to wystarczy aby komunikat def get_photo ment ścieżki, czyli newindex to naturalnie na- został wyświetlony przez przeglądarkę. Ten @photo=Photo.find(params[:id]) zwa kontrolera. Nazwa tego pliku, a dokładniej prosty przykład pokazuje, jak następuje prze- send_data(@photo.picture, : rozszerzenie rhtml, zdradza, iż plik ten zostanie twarzanie informacji. type=> 'image/jpeg') poddany dodatkowej obróbce przez Ruby'ego: Zostało ono podzielone na dwa etapy. Etap end pierwszy to przetwarzanie pliku z implementacją def show
Bardzo ważny komunikat
metody index. W drugim etapie utworzone in- @photo = Photo.find(params[: <%= @messageToTheWorld %> formacje mogą zostać przeniesione do formatki. id]) end def new @photo = Photo.new Instalacja języka Ruby oraz pakiety RubyOnRails end Wiele dystrybucji oferuje gotowe pakiety z językiem Ruby, toteż nie trzeba samo- def create dzielnie instalować odpowiedniego pakietu. Jednak z drugiej strony może się oka- @photo = Photo.new(params[: zać, iż wersja dostępna w pakiecie może być nieco nowsza niż dostępna w systemie. photo]) W takim przypadku warto zainstalować Ruby'ego samodzielnie ze zródeł. if @photo.save Nie jest to trudne zadanie. W pierwszej kolejności ściągamy ze strony głównej pro- flash[:notice] = 'Zdjęcie jektu Ruby najnowszą wersję stabilną. Następnie dekompresujemy archiwum: oraz opis zostały wpisane do bazy danych.' tar zxvpf ruby-1.8.6.tar.gz redirect_to :action => 'list' Ponieważ obecny jest skrypt configure, za pomocą trzech poleceń dokonamy konfiguracji, else kompilacji oraz instalacji w systemie: render :action => 'new' end ./configure prefix=/opt end make def edit make install @photo = Photo.find(params[: Drugim ważnym krokiem jest instalacja pakietu gem, za pomocą którego można instalować id]) dodatkowe rozszerzenia do Ruby'ego. Tym razem ściągamy archiwum o nazwie np. ru- end bygems-1.0.1.tgz. Dekompresujemy archiwum, ale sposób instalacji jest innym, bowiem def update należy wydać polecenie: @photo = Photo.find(params[: id]) ruby setup.rb if @photo.update_ attributes(params[:photo]) Powyższe polecenie wykona wszystkie niezbędne czynności związane z instalacją gema. flash[:notice] = 'Zdjęcie W tym momencie możemy zainstalować dodatkowy program, użyteczny podczas pracy z oraz opis zostały uaktualnione.' pakietem RubyOnRails, w następujący sposób: redirect_to :action => 'show', :id => @photo gem install rake else Kolejny krok to instalacja środowiska Rails za pomocą następującego polecenia: render :action => 'edit' end gem install -v=1.2.6 rails --include-dependencies end end Zgodnie z naszymi ustaleniami instalujemy wersję 1.2.6, natomiast opcja --include-de- pendencies zapewnia, iż zostaną zainstalowany dodatkowe pakiety np. ActiveRecord uła- Listing 5. Trywialna formatka odpowiedzialna za twiający obsługę bazy danych. wyświetlenie nazw poszczególnych kolumn Nasza aplikacja wymaga jeszcze instalacji sterownika do bazy MySQL oraz serwera
Mongrel, co wykonamy w następujący sposób: <% for column in Photo.content_ columns %> gem install mysql
Należy jeszcze dokonać instalacji bazy danych MySQL. Jednakże najlepiej zainstalować ba- <% end %> zę z pakietów dostępnych w danej dystrybucji ponieważ zaoszczędzi nam to trochę czasu.
52 lipiec/sierpień 2008 Programowanie Ruby/RubyOnRails Plan naszej aplikacji Listing 6. Formatka odpowiedzialna za wyświetlanie listy zdjęć wprowadzonych do bazy danych Naszym głównym zadaniem jest opracowa-
Dostępne zdjęcia
nie nieskomplikowanego systemu, w którym
użytkownik będzie mógł umieszczać dane:
zdjęcie, jego opis oraz imię i nazwisko auto-
Zdjęcie
ra. Zostaną one umieszczone w bazie danych,
Opis
w naszym przypadku będzie to baza danych
Imię i nazwisko
oparta o serwer MySQL.
Oznacza to konieczność utworzenia tabe- <% for photo in @photos %> li. Dla naszej bardzo prostej bazy danych jest
to kilka linii, co potwierdza Listing 2. Ma-
my cztery proste pola. Pierwszym jest id, photo.id ) %>" czyli główny klucz tabeli. Drugie pole o na- height="100" /> zwie picture będzie zawierać zdjęcie prze-
słane przez użytkownika. Jego opis znajdzie
się w polu description, a imię i nazwisko <%= photo.send("description") %> w polu name.
Możliwości naszej aplikacji nie bę-
dą zbyt duże, tzn. użytkownik będzie mógł <%= photo.send("name") %> tylko wgrać plik ze zdjęciem do naszej ba-
<%= link_to 'Skasuj zdjęcie', { :action => 'destroy', :id => photo usunąć bądz zmienić, jak również będzie ist- }, :confirm => 'Czy na pewno?', :post => true %>
niała możliwość zmiany opisu. Dla ułatwie-
nia nie będziemy wprowadzać kont użytkow- <% end %> nika, które naturalnie w prawdziwym serwi-
sie byłyby niezbędne. <%= link_to 'Poprzednia strona', { :page => @photo_pages.current.previous } if @photo_pages.current.previous %> Konfiguracja bazy danych <%= link_to 'Następna strona', { :page => @photo_pages.current.next } if Tworzenie aplikacji powinniśmy zacząć od @photo_pages.current.next %> utworzenia bazy danych. Użyć możemy np.
programu mysql i z poziomu konsoli utwo- <%= link_to 'Nowe zdjęcie', :action => 'new' %> rzyć odpowiednią tabelę. Warto także sko- rzystać z narzędzi graficznych takich jak My- Listing 7. Metoda o nazwie up, która tworzy tabelę ze zdjęciami SQL GUI, gdzie łatwo będzie utworzyć tablę oraz poddać ją edycji. Będzie można także def self.up sprawdzić nowe rekordy, które będą wpisy- create_table :photos do |t| wane z poziomu naszej aplikacji sieciowej. t.column :picture, :mediumblob Drugim elementem jest określenie sposo- t.column :description, :text bu dostępu do danych z poziomu naszej apli- t.column :name, :text kacji sieciowej. Należy poddać edycji plik da- end tabase.yml, który znajduje się w katalogu con- end fig. W tym pliku określamy nazwę użytkow- nika, hasło, nazwę bazy danych oraz nazwę Listing 8. Formularz za pomocą którego wprowadzamy nowe zdjęcie do bazy hosta na którym został uruchomiony serwer. Podczas pracy nad aplikacją serwer MySQL <%= start_form_tag( { :action => 'create' }, :multipart => true ) %> warto naturalnie uruchamiać na lokalnej ma- <%= render :partial => 'form' %> szynie. Ogólne warto wspomnieć, iż aplikacje <%= submit_tag "Nowe zdjęcie" %> Rails są uruchamiane w trzech podstawowych <%= end_form_tag %> trybach: test, development oraz production <%= link_to 'Lista fotografii', :action => 'list' %> (o tym, w jakim trybie uruchamiana jest apli- kacja decyduje wartość zmiennej środowisko- Listing 9. Fragment formularza odpowiedzialnego za tabelę ze zdjęciami wej RAILS_ENV). Pierwszy tryb jest przezna- czony tylko do testów, drugi tryb jest stosowa- <%= error_messages_for 'photo' %> ny podczas tworzenia aplikacji i ten tryb bę-
dziemy stosować. Ostatni tryb produkcyj- <%= file_field 'photo', 'photo' %>
ny przeznaczony jest do codziennej pracy aplikacji. www.lpmagazine.org 53 Programowanie Ruby/RubyOnRails Listing 3 zawiera przykładowy plik da- Znaczenie pozostałych metod jest nastę- zostanie tylko jedna z nich odpowiedzialna tabase.yml. W sekcji development stosu- pujące: metoda get_photo pobiera dane ob- za listę zdjęć. jemy sterownik MySQL, natomiast w sek- razu w formacie jpeg, metoda show jest od- Listing 6 przedstawia cały kod zródłowy cji production widać jeszcze wpis związa- powiedzialna za wyświetlenie zdjęcia oraz tej formatki. W pewnym sensie składa się ona ny z dostępem do bazy danych poprzez ste- opisu, jednakże ta metoda tylko inicjuje ten z trzech sekcji. Pierwsza to tabela, w której rownik jdbc. proces za poprawne wyświetlenie danych znajduje się lista zdjęć. odpowiedzialna jest formatka show.rhtml. Druga sekcja to menu ze stronami, jeśli Kontroler aplikacji Zadaniem metody create jest umiesz- baza zawiera dużą ilość zdjęć. Trzecia sekcji Wszystkie podstawowe metody obsługują- czenie danych w bazie danych. Metoda składa się z jednego przycisku odpowiedzial- ce naszą aplikację znajdują się w kontrolerze ta jest wywoływana z poziomu formatki nego za wprowadzanie nowej fotografii. photos. Kontroler ten tworzymy w typowy list.rhtml. Jak widać, oprócz kontrolera Pierwszy element tabeli ze zdjęciami to dla pakietu Rails sposób (będąc naturalnie w istotną rolę ogrywają formatki, gdzie może- nagłówek z nazwami kolumn. Rozwiązanie, katalogu naszej aplikacji): my reagować na czynności użytkownika. jakie prezentuje Listing 6 jest bezpośrednie, gdyż tworzymy w HTML nagłówek naszej ruby script/generate controller photos Lista zdjęć tabeli wpisując bezpośrednio nazwy kolumn. W naszej aplikacji występuje kilka forma- Możemy jednak zautomatyzować ten proces Obszerne fragmenty kontrolera zawarta tek. Z racji ograniczonego miejsca opisana postępując tak jak pokazano na Listingu 5. są na Listingu 4. Brakuje tam jednak kil- ku metod, które pokrótce omówimy w tym miejscu. Pierwsza to naturalnie metoda index, któ- rej zadaniem jest wyświetlenie spisu wprowa- dzonych do bazy danych zdjęć. Jej kod jest bardzo krótki i przedstawia się następująco: def index list render :action => 'list' end Wywołujemy inną metodę list, bezpośred- nio odpowiedzialną za obsługę listy zdjęć, a następnie akcją render niejako przekazu- jemy sterowanie do akcji list. Sposób implementacji metody list rów- nież nie jest zbyt obszerny: def list @photo_pages, @photos = paginate :photos, :per_page => 6 end Rysunek 3. Pogląd na tabelę ze zdjęciami w programie MySQL GUI Wykorzystujemy dostępną metodę paginate, której zadaniem jest podział wszystkich wpi- Automatyczne tworzenie tabeli ze zdjęciami sów w bazie na strony po sześć zdjęć na jed- ną stronę. Na początku tworzenia naszej aplikacji tabela z danymi została utworzona przez nas Wynikiem działania metody jest obiekt samodzielnie. Zadanie to może zostać zrealizowane za pomocą RubyOnRails. Nale- reprezentujący naszą bazę photos oraz obiekt ży bowiem na początku utworzyć specjalny obiekt w następujący sposób: photo_pages wykorzystywany do nawigacji ruby script/generate migration create_photos po stronach. W katalogu db/migration zostanie utworzony 001_create_photos.rb. W tym pliku, trzeba zmienić definicję metody self.up na następującą (Listing 7.) W wywołaniu następującego polecenia: Na płycie CD/DVD Na płycie CD/DVD znajdują się wy- rake db:migration korzystywane biblioteki, kod zródłowy tabela ze zdjęciami zostanie samodzielnie utworzona. Wymaga to naturalnie poprawnej programu oraz wszystkie listingi z ar- konfiguracji dostępu do bazy danych w pliku database.yml. tykułu. 54 lipiec/sierpień 2008 Programowanie Ruby/RubyOnRails Ruby samodzielnie utworzy odpowiedni na- Kolejne elementy tabeli to polecenia do edy- opisem. Jednakże jak widać nie definiujemy główek. Niestety nazwy kolumn będą w języku cji zdjęcia, wyświetlenia pojedynczego zdję- pól, gdzie użytkownik może podać np. opis, angielskim, ponieważ nasza tabela została w ta- cia oraz - w końcu - do skasowania go za po- ale korzystamy z oddzielnego pliku o nazwie ki sposób utworzona. Możemy naturalnie utwo- mocą metody destroy znajdującej się kon- _from.rhtml, który zostanie wczytany za po- rzyć polskie nazwy kolumn, jednak wiele syste- trolerze. Przed skasowaniem zadajemy pyta- mocą linii kodu: <%= render :partial => mów baz danych nie obsługuje poprawnie zna- nie za pomocą akcji confirm czy istotnie ska- 'form' %>. Dlatego formularz dodający no- ków narodowych w nazwach tabel i pól, to- sować zdjęcie. we zdjęcie jest tak krótki: też najlepiej pozostać przy nazwach pisanych Metoda destroy zostanie wywołana w mo- W podobny sposób postępujemy w przy- po angielsku lub pozbawionych polskich zna- mencie uzyskania odpowiedzi twierdzącej. padku formularza edit.rhtml. Ponownie ko- ków, a nazwy kolumn wprowadzić bezpośred- rzystamy z pliku _from.rhtml którego począt- nio do formatki.
<%= link_to 'Skasuj zdjęcie', { : kowy fragment przedstawia się następująco: Po definicji nagłówka za pomocą pętli bę- action => 'destroy', :id => photo }, Pierwsza linia będzie odpowiedzialna za dziemy wyświetlać poszczególne zdjęcia. Pę- :confirm => 'Czy na pewno?', :post => sprawdzania poprawności wprowadzonych tla ta rozpoczyna się od wyrażenia: true %>
danych. Natomiast drugi fragment określa po- le, gdzie użytkownik będzie mógł podać na- <% for photo in @photos %> Nowe zdjęcie zwę pliku ze zdjęciem. Pojawi się też przycisk Formatka odpowiedzialna za wczytanie no- o nazwie Przeglądaj, za pomocą którego bę- Wyświetlenie np. opisu, to definicja komórki ta- wego zdjęcia jest bardzo krótka. Jej najważ- dzie można wybrać plik do wysłania na ser- beli i odpowiednie wyrażenie w języku Ruby: niejszy fragment to utworzenie formularza za wer. Definicja pola opis również jest krótka: pomocą tagu start_form_tag. Po naciśnię-
<%= photo.send("description") ciu przycisku z etykietą Nowe zdjęcie do ba-
<%= text_area 'photo', 'description' %>
Podsumowanie Opisana aplikacja, choć bardzo prosta, ukazu- je największą zaletę pakietu RubyOnRails: ła- twość używania tego systemu. Jednym z przy- kładów jest obsługa bazy danych. Tylko na sa- mym początku należało odnieść się do języka SQL. Opracowanie tego typu aplikacji siecio- wej przy pomocy pakietu Rails to jak widać praca na przysłowiowy jeden wieczór. Podobna baza opracowana w PHP wy- magałaby od nas znacznie większego nakła- du pracy, gdyż pakiet RubyOnRails uwalnia nas od wielu czynności o charakterze czysto technicznym. Tworzenie tabel czy też zadawanie odpo- Rysunek 4. Środowisko Aptana wiednich zapytań to zadanie dla pakietu Ru- byOnRails ta właściwość na skupieniu się na tworzeniu samej aplikacji. Dlatego zachę- cam do wprowadzania zmian w przedstawio- O autorze nej bazie danych, bo możliwości są wręcz Autor zajmuje się tworzeniem oprogramowania dla WIN32 i Linuksa. Zainteresowania: nieograniczone. Można dodać do bazy dodat- teoria języków programowania oraz dobra literatura. kowe pola, jak np. datę dodania zdjęcia oraz Kontakt z autorem: autorzy@linux.com.pl. popracować nad stroną graficzną. W Sieci " http://www.ruby-lang.org/pl Strona o języku Ruby " http://www.rubyonrails.org/ Główna strona o pakicie RubyOnRails " http://www.rubyonrails.pl/ Wersja polska strony o RubyOnRails " http://freeride.rubyforge.org/wiki/wiki.pl Środowisko IDE do pracy z Ruby'm " http://www.aptana.com/rails/ RadRails oraz środowisko Aptana " http://en.wikipedia.org/wiki/List_of_web_application_frameworks Spis innych systemów do tworzenia aplikacji Internetowych www.lpmagazine.org 55