Ruby on Rails

Coś bardziej zaawansowanego

Stworzymy teraz projekt, dla którego struktura modelu wczytana będzie z bazy danych.

Tworzymy nowy projekt

Do stworzenia projektu wykorzystamy wspomnianej wcześniej wtyczki do Eclipse'a: "RadRails"
Zaczynamy, tworzymy nowy projekt i nazywamy go "depot":

nowyProjekt

Baza danych:

Jak już wspomnieliśmy, chcemy model dla naszego projektu wczytać z bazy danych. W tym celu musimy stworzyć konieczne tabelki, zatem pobieramy 2 skrypty do stworzenia bazy danych i tabel i umieszczamy je w katalogu db w naszym projekcie.
Skrypty możemy odpalić przez phpmyadmin lub za pomocą konsoli:

mysql –u root –p
>source sciezka_do_pliku/createDB.sql
tagi
przełączamy sie do bazy danych
use depot_development
I wczytujemy skrypt do stworzenia tabel w bazie danych.
>source sciezka_do_pliku/create.sql
tagi

Konfiguracja aplikacji:

Aby aplikacja wiedziała co się dzieje w bazie danych, należy wyedytować plik config/database.yml.
tagi
Wstawiamy odpowiednie wartości czyli localhost user name i password.
Czas wygenerować model na podstawie dodanych tabel.

depot> ruby script/generate scaffold Product Admin
depot> ruby script/server

I czas na odrobinę czarów:http://localhost:3000/admin

tagi

Jak widać na powyższym obrazku, możemy dodawać, usuwać i edytować nowe produkty.
Wszystko za sprawą kilku linii kodu. No dobrze, skoro już mamy możliwość dodania produktów, to przeprowadźmy mały test. Spróbujmy dodać produkt bez nazwy.

Walidacja

Otrzymaliśmy komunikat o błędzie, co nie jest może zaskoczeniem, ale warto zwrócić uwagę na rodzaj tego błędu. Nasz system starał się dodać do bazy danych rekord z pustą wartością wymaganą. Jest to "groźna" przypadłość, której należy się jakoś pozbyć. O czym każdy wie, należy sprawdzić pola formularza przed wysłaniem. O czym nie każdy wie, możemy to wykonać za pomocą jednej linijki w modelu produktu, ponieważ wszelkie dane jakie przychodzą i wychodzą z aplikacji do bazy idą (a przynajmniej powinny ) przez model.
Otwieramy app/models/product.rb

class Product < ActiveRecord::Base
end

Po ilości kodu łatwo domyślić się iż cała obsługa została odziedziczona z klasy bazowej. Dodajmy do naszej klasy linijkę walidującą:

class Product < ActiveRecord::Base
validates_presence_of :title, :description, :image_url
end

Możliwości walidacji jest oczywiście więcej. Możemy np. sprawdzać, czy podana wartość jest liczbą, przy użyciu :

validates_numericality_of :price

Dzięki tym zabiegom, przy próbie dodania błędnych danych, system poinformuje nas "ładnym" komunikatem o błędach:

tagi

Jak już walidujemy, to ustawiamy jeszcze aby tytuł był unikalny

validates_uniqueness_of :title

Możemy oczywiście napisać własne reguły walidacyjne.
Sprawdźmy np. czy ścieżki obrazków zaczynały sie http I kończyły gif, jpg, lub png

validates_format_of :image_url,
:with => %r{^http:.+\.(gif|jpg|png)$}i,
:message => "popraw ścieżkę do obrazka"

Teraz jeszcze tylko sprawdzimy czy cena jest większa od zera.

protected
def validate
errors.add(:price, "Ustaw cene wieksza od zera") unless price.nil? || price > 0.0
end

Na koniec nasza klasa powinna wyglądać tak:

tagi

Wygląd

Czas najwyższy poprawić wygląd naszej storny.
Jeśli oddaliśmy już kilka produktów, to zdajemy sobie sprawę, iż należało by zmienić sposób ich listowania. Jeśli zajrzymy do katalogu app/views/admin/ to zobaczymy tam 5 plików o rozszerzeniu .rhtml.
W przeciwieństwie do poprzedniego projektu, w którym nasz plik rhtml był całą stroną, tu mamy do czynienia, z plikami odpowiedzialnymi tylko za część. Nas interesuje plik odpowiedzialny za wylistowanie czyli list.rhtml.

Aby go nie przepisywać, proponuję zastąpić go tym. Zanim odpalimy naszą stronę by sprawdzić "nową jakość", niezbędna będzie zmiana w pliku scaffold.css, którą znów najłatwiej będzie wykonać poprzez zastąpienia pliku public/stylesheets/scaffold.css wersją ze strony:scaffold.css.

Zanim omówimy zmiany, sprawdźmy efekt: http://localhost:3000/admin/list

tagi

No dobrze, a czemu zawdzięczamy ten efekt?

listowanie

Z ciekawych rzeczy, które się wydarzyły to polecam bliższej uwadze:
  • for product in @products - pozwala na wygodne przechodzenie po rekordach
  • <tr valign="top" class="ListLine<%= odd_or_even %>"> - ta linijka w łatwy sposób rozwiązuje sprawę kolorowania wierszy różnymi kolorami
  • przy różnych polach mamy do czynienia z różnym formatowaniem przy pomocy funkcji - sprintf()
  • h(product.title) - funkcja h() pozwala przetworzyć tekst w sposób bezpieczny dla przeglądarki np. zamiana > na &gt;
  • godnym zauważenia jest także <%= link_to 'New product', :action => 'new' %> - jest to mechanizm tworzenia linków do stron odpowiadających odpowiednim akcjom


Drugi kontroler

By nasz projekt stał się nieco ciekawszy, stworzymy teraz drugi kontroler odpowiedzialny za dostęp dla listy produktów, dla klientów naszego internetowego sklepu.
Zaczynamy zatem standardowo od stworzenia kontrolera:

depot> ruby script/generate controller Sklep index

Jak widać poprosiliśmy o stworzenie dodatkowo akcji index. Akcja ta jest domyślną akcją odpalaną na kontrolerze, dlatego to w niej umieścimy kod odpowiedzialny za wylistowanie produktów.

Aby uczynić nasz przykład "ciekawszym", będziemy przedstawiać tylko produkty dostępne. W tym celu musimy stworzyć metodę klasową na Klasie Product. Należy zatem w pliku app/models/product.rb dodać metodę:

produkty

Dwie nowe rzeczy pojawiły się w tym kawałku kodu:
  • słowo self - jest to sposób wprowadzania metod klasowych
  • metoda find - która na kształt zapytań SQL zwraca nam kolekcję obiektów spełniających odpowiednie warunki, w zadanym porządku

Jesteśmy gotowi do dodania logiki do naszej akcji index w kontrolerze Sklep:

produkty

Oczywiście nie obejdzie się bez zmiany domyślnego widoku. Zastąpmy zatem nasz plik app/views/sklep/index.rhtml plikiem index.rhtml.

Sprawdźmy co otrzymaliśmy http://localhost:3000/sklep/

Do ostatecznego wyniku brakuje nam już tylko dwóch kroków. Jak wspomnieliśmy wcześniej przy edycji widoku dla kontrolera Admin, Możemy stworzyć główny template, który aplikowany jest dla każdej akcji z danego kontrolera. Stwórzmy zatem plik sklep.rhtml i umieśćmy go w katalogu app/views/layouts

produkty

Poza całym mnóstwem magii, w naszym templejcie znajduje sie linijka <%= @content_for_layout %> - to w to miejsce zostanie wstawiona treść widoku konkretnych akcji. Na koniec dodajemy jeszcze jeden CSS depot.css i zastępujemy nasz plik index.rhtml nowym plikiem index.rhtml

Jeśli wszystko poszło zgodnie z planem, to możemy sprawdzić efekt naszej pracy http://localhost:3000/sklep/

Projekt jest dostępny w formie paczki tutaj.