UWAGA: ten tutorial służył jako pomoc dla początkujących podczas sesji hands-on na pierwszym spotkaniu KRUG, odnosi się więc do Rails w wersji z września 2006.
Prerekwizyty:
- zainstalowana baza danych mysql
- zainstalowane railsy
Tworzenie projektu
Tworzymy prostą aplikację do zarządzania zbiorami domowej biblioteki.
W przypadku Eclipse z pluginem RadRails:
Wybieramy File->New->Project, z listy projektów wybieramy Rails -> Rails Project. W oknie dialogowym wpisujemy nazwę aplikacji – ‘library’, zostawiamy zaznaczone checkboxy ‘Generate Rails application skeleton’ i ‘Create a WEBrick server’ oraz odznaczone ‘Disable table pluralization’.
W innym przypadku:
Aby założyć nowy projekt uruchamiamy polecenie: rails library
Aby zaimportowac do eclipsa można utworzyć nowy `General Project`, a następnie skorzystać z opcji Import -> General -> File System.
Uruchamianie aplikacji:
Następnie przechodzimy do nowo utworzonego katalogu ‘library’ i uruchamiamy aplikację:
cd library
script/server &W przeglądarce wpisujemy adres: http://localhost:3000. Powinna pojawić się strona:
`Welcome aboard You’re riding the Rails!`
Struktura katalogu aplikacji
Railsy wygenerowały drzewo katalogów aplikacji. Zgodnie z zasadą `convention over configuration`, dzięki umieszczaniu plików źródłowych i niezbędnych danych konfiguracyjnych w przewidzianych do tego miejscach, railsy znajdują je same.
Funkcje najważniejszych katalogów:
- app: pliki źródłowe aplikacji
- config: pliki konfiguracyjne
- db: pliki związane z bazą danych
- lib: dodatkowe biblioteki
- log: logi
- public: javascript’y, css’y, obrazki oraz konfiguracja webservera
- test: testy
- vendor: pluginy
Organizacja plików źródłowych
W railsach aplikacje tworzy się według wzorca projektowego Model-View-Controller. Źródła aplikacji w katalogu app są zorganizowane w sposób odzwierciedlający ten podział:
- controllers: kontrolery, łączą widok z modelem
- models: modele, zawierają logikę
- views: widoki, zorganizowane w podkatalogach o nazwach odpowiadającym poszczególnym kontrolerom
- helpers: funkcje pomocnicze dla widoku
Tworzenie bazy danych
Za pomocą wybranego klienta mysql tworzymy pustą bazę danych o nazwie library_development:
create database library_development;Konfigurujemy połączenie z bazą danych edytując plik config/database.yml. Odszukujemy w nim sekcję ‘development’ i uzupelniamy dane takie jak nazwa uzytkownika i hasło do łączenia się z bazą:
development:
adapter: mysql
database: library_development
username: some_user
password: some_pass
host: localhostW przypadku błędu dotyczącego pliku /tmp/mysql.sock przy próbie połączenia, najłatwiej poradzić sobie zmieniając pole host z localhost na 127.0.0.1. Edycja schematu bazy danych – migracje
Aby utworzyć pierwszą tabelę w katalogu ‘library’ wpisujemy polecenie:
script/generate migration create_booksW katalogu db pojawił się nowy plik ‘001_create_books.rb’. Przechodzimy do jego edycji. Jest to plik tzw. migracji, służący do zmiany definicji struktury bazy danych w sposób przyrostowy; kolejne zmiany bazy umieszczane są w kolejno numerowanych plikach. Jest to mechanizm ułatwiający współpracę wielu programistów nad schematem bazy danych.
W pliku migracji znajdują się dwie metody – up i down (a dokładnie self.up i self.down, co oznacza metody klasowe). W up definiujemy zmianę bazy danych, w down sposób odwrócenia tej zmiany. W metodach tych można korzystać ze specjalnej składni rubiego do definiowania schematu bazy, lub ze zwykłego sqla. Pierwsza migracja służy utworzeniu tabeli z książkami:
class CreateBooks < ActiveRecord::Migration
def self.up
sql=<<-END_SQL
CREATE TABLE books(
id int(11) AUTO_INCREMENT PRIMARY KEY,
title varchar(256),
author varchar(256)
)
END_SQL
execute sql
end
def self.down
execute 'DROP TABLE books'
end
endMetoda execute przyjmuje napis (string) zawierający polecenia sql. W metodzie up skorzystaliśmy ze składni umożliwiającej przypisanie do zmiennej sql wieloliniowego stringa. W metodzie down zamiast tego skorzystaliśmy z najprostszej postaci stringa w rubim.
Następnie uruchamiamy w katalogu library polecenie:
rake migratei jeśli poprawnie skonfigurowaliśmy połączenie z bazą i nie ma błędów sql w migracji, powinniśmy zobaczyć:
== CreateBooks: migrating =====================================================
-- execute(` CREATE TABLE books(
title varchar(256),
author varchar(256)
)
`)
-> 0.0477s
== CreateBooks: migrated (0.0483s) ============================================a w bazie danych powinna istnieć tabela books.
Scaffold
Utworzymy prosty interfejs do zarządzania książkami w bibliotece, wykorzystując do tego mechanizm generowania scaffoldu – `rusztowania`.
W katalogu library uruchamiamy:
script/generate scaffold booka następnie wpisujemy w przeglądarce adres: http://localhost:3000/books
Pokazuje się strona z pustą listą książek. Gdy dodamy pierwszą książkę okaże się, że scaffold obsługuje również edycję, kasowanie i widok szczegółów książki. Cały odpowiedzialny za to kod można znaleźć w katalogu naszej aplikacji:
- app/controllers/books_controller: zawiera definicje akcji dostępnych z www. Jak widać ich nazwy odpowiadają url’om w następujący sposób: nazwa_kontrolera/nazwa_akcji
- app/models/book: model, klasa obsługująca tabelę books. Jak widać jest pusta – wszystkie metody do operowania na danych są generowane dynamicznie
- app/views/books: widoki dla wszystkich akcji. Warto zwrócić uwagę na sposób współdzielenia fragmentów widoku poprzez mechanizm partiali – w tym wypadku widoki new i edit korzystają z wspólnego formularza _form.rhtml.
Relacja wiele do jeden
Utworzona tabela z książkami ma poważnż wadę – imię i nazwisko autora wpisywane jest w jednym polu, na dodatek dla każdej książki napisanej przez tego samego autora trzeba je wprowadzać ponownie. Poprawimy to tworząc nową tabelę authors, oraz definiując odpowiednią relację między autorami i książkami.
script/generate migration create_authorsEdytujemy plik migracji:
class CreateAuthors < ActiveRecord::Migration
def self.up
sql=<<-END_SQL
CREATE TABLE authors(
id int(11) AUTO_INCREMENT PRIMARY KEY,
name varchar(128),
surname varchar(128)
)
END_SQL
execute sql
end
def self.down
execute 'DROP TABLE authors'
end
endrake migratePrzy okazji można zaobserwować, że w naszej bazie danych istnieje mała tabela schema_info, o jednym polu – version. Po pierwszej migracji pole to miało wartość jeden, po drugiej zostało zwiększone – pole to oznacza aktualną wersję migracji w danej bazie.
Tworzymy scaffold dla tabeli authors:
script/generate scaffold authori pod adresem http://localhost:3000/authors mamy dostępny interfejs do zarządzania autorami.
Teraz należy zmienić definicję tabeli books, zamieniając w niej pole author na identyfikator autora. Robimy to oczywiście poprzez nową migrację: script/generate migration remove_author_and_add_author_id_to_books
Nazwa może się wydawać długa, ale lepiej stosować nazwy opisujące co dana migracja robi oraz aby uniknąć duplikacji nazw migracji, co powoduje błędy.
class RemoveAuthorAndAddAuthorIdToBooks < ActiveRecord::Migration
def self.up
execute 'ALTER TABLE books DROP COLUMN author'
execute 'ALTER TABLE books ADD COLUMN author_id int(11) REFERENCES authors(id)'
end
def self.down
execute 'ALTER TABLE books DROP COLUMN author_id'
execute 'ALTER TABLE books ADD COLUMN author varchar(256)'
end
endW migracji moglibyśmy zdefiniować sposób postępowania z danymi z poprzedniej struktury,tak aby ich nie utracić. Nie robimy tego w tym przykładzie, więc najlepiej te dane wyrzucić z bazy, bo będą powodowały błędy.
Po wykonaniu migracji:
rake migrateprzejdźmy do edycji modelu book. Dopiszmy jedną linijkę:
class Book < ActiveRecord::Base
belongs_to :author
endDefiniujemy w ten sposób relację wiele do jeden między książkami a autorem.
Aby móc wybrać autora książki przy jej tworzeniu, musimy wprowadzić zmiany do widoku i kontrolera.
W kontrolerze books dodajemy jedną linię do akcji new:
def new
@authors = Author.find_all
@book = Book.new
end<%= text_area 'book', 'author' %><%= select 'book', 'author_id', @authors.collect{|a| [a.name+' '+a.surname,a.id]} %><p>
<b>Title:</b> <%= h @book.title %>
</p>
<p>
<b>Author:</b> <%= h @book.author.name+' '+@book.author.surname %>
</p>
<%= link_to 'Edit', :action => 'edit', :id => @book %> |
<%= link_to 'Back', :action => 'list' %>Teraz wyświetla się imię i nazwisko autora. Warto tu zwrócić uwagę na następujące szczegóły:
- obiekt @book ma metod? author – jest ona generowana dynamicznie na podstawie zdefiniowanej relacji belongs_to
- metoda h w widoku służy do wyświetlania napisów czyszcząc je z potencjalnie szkodliwych elementów, jakimi mogą być na przykład złośliwy html czy javascript. Powinna być stosowana zawsze przy wypisywaniu danych wprowadzonych przez użytkownika.
Można też zauważyć, że po raz drugi do wypisania imienia i nazwiska autora skorzystaliśmy z konkatenacji stringów (wcześniej przy tworzeniu książki), przez co wprowadziliśmy duplikację. Możemy to naprawić na kilka sposobów, najprostszy to utworzenie w modelu author nowego wirtualnego atrybutu full_name:
def full_name
name+' '+surname
end<%= select 'book', 'author_id', @authors.collect{|a| [a.name+' '+a.surname,a.id]} %><%= select 'book', 'author_id', @authors.collect{|a| [a.full_name,a.id]} %><%= h @book.author.name+' '+@book.author.surname %><%= h @book.author.full_name %>Relacja jeden do wielu
Gdybyśmy chcieli, aby w widoku szczegółów autora ukazywała się lista książek przez niego napisanych, musielibyśmy zdefiniować relację `w drugą stronę`, czyli jeden do wielu, w modelu autor:
class Author < ActiveRecord::Base
has_many :books
def full_name
name+' '+surname
end
endDzięki temu obiekty klasy Author zyskują dodatkowe metody dotyczące powiązanych z nimi książek, między innymi metodę books zwracającą książki tego autora. Można więc dodać w /views/authors/show.rhtml następujący fragment:
<p><b>Books:</b></p>
<ul>
<% @author.books.each do |b| -%>
<li><%= b.title%>
<% end -%>
</ul>Walidacje
Obecnie nasza aplikacja nie protestuje przy tworzeniu autorów i książek zostawiając puste pola formularza. Skutkuje to tworzeniem pustych obiektów w bazie. Aby temu zapobiec, można skorzystać z mechanizmu walidacji w railsach.
Ponieważ nasze modele są minimalistyczne, wszystkie pola powinny być obowiązkowe:
class Author < ActiveRecord::Base
has_many :books
def full_name
name+' '+surname
end
validates_presence_of :name, :surname
end
class Book < ActiveRecord::Base
belongs_to :author
validates_presence_of :title, :author_id
endPróba submitu pustego formularza skutkuje teraz wypisaniem listy błędów dotyczących poszczególnych pól. Rails udostępnia również wiele innych podstawowych walidacji, jak validates_uniqueness_of czy validates_format_of.
Podsumowanie
W zamyśle powyższy mini tutorial ma na celu zaprezentowanie najczęściej wykorzystywanych elementów frameworku rails i dostarczenie pewnej ilości bazowego kodu do samodzielnego eksperymentowania. Powodzenia!
Podstawowe źródła:
- http://api.rubyonrails.org/
- http://wiki.rubyonrails.org/rails
