Ruby on Rails (12-14) 2008/2009

Z JASR Wiki

<<< Powrót do Tworzenie aplikacji wielowarstowych 2008/2009

Spis treści

Wprowadzenie

Ruby on Rails (RoR) jest to framework przystosowany do tworzenia aplikacji webowych w schemacie MVC w jezyku Ruby. Główne założenia projektu to:

  • szybkość pisania kodu (nie powtarzmy raz podanej informacji)
  • konwencja nad konfiguracją - jak najmniej konfiguracji - promujemy konwencje i wzorce domyślne

Ruby

Ruby to interpretowany, w pełni obiektowy i dynamicznie typowany język programowania stworzony w 1995 roku przez Yukihiro Matsumoto (pseudonim Matz).

Cechy szczególne

Ruby posiada:

  • automatyczne odśmiecanie pamięci
  • iteratory
  • przeciążanie operatorów
  • obsługa wyjątków
  • wyrażenia regularne wbudowane w składnię
  • liczby całkowite o dowolnych rozmiarach
  • dodawanie metod do instancji klasy - możliwa jest zmiana lub dodanie metody do instancji danej klasy
  • przekazywanie funkcji jako parametrów
  • rozpoznawanie typów na podstawie ich zachowania, a nie deklaracji
  • moduły - rodzaj wielodziedziczenia pozwalający włączyć gotową implementację zbioru metod do danej klasy
  • możliwość zmiany praktycznie wszystkiego (w wyniku możliwości dokonywania zmian w już zadeklarowanych klasach)

Instalacja

  • Linux - zainstalowny w większości dystrubycji, również na komputerach w labolatorium
  • Windows - instalator

Interpretery

  • Ruby - oryginalna w C
  • JRuby - ruby na JVM
  • IronRuby - ruby na .NET

Przydatne narzędzia

  • ri - pomoc, np. "ri String" opisze klasę String
  • irb - ruby jako interpreter - przydatny do testowania przykładów poniżej
  • RubyGems - menadżer pakietów dla Ruby obsługujący zależności

Wprowadzenie do języka

Przykłady poniżej pochodzą z podręcznika.

Podstawy


# komentarz 1-linijkowy

=begin
komentarz wielolinijkowy
znak '=' musi być w pierwszej kolumnnie!
=end

#wypisz na wyjście
puts "ala ma kota"

#wszystko jest obiektem (zamień na system binarny - w postaci stringa)
345.to_s(2)

#liczby mogą być dowolnego rozmiaru
c = 1000**100
puts c

Definicja funkcji

Możliwości:

  • definiowanie funkcji z dowolną ilością argumentów
  • przekazywanie bloków kodu do funkcji
  • specyfikowanie domyślnych wartości
def sayGoodnight(name = "John")
  result = "Goodnight, " + name
  return result
end


# Time for bed...
puts sayGoodnight
puts sayGoodnight("Mary")

Tablice

a = [ 1, 'cat', 3.14 ]
puts a[1]


#pusta tablica
empty1 = []
empty2 = Array.new #konstruktor z klasy Array

#notacja skrótowa dla słów
a = %w{ ant bee cat dog elk }

#tablica asocjacyjna
instSection = {
  'cello'     => 'string',
  'clarinet'  => 'woodwind',
  'drum'      => 'percussion',
  'oboe'      => 'woodwind',
  'trumpet'   => 'brass',
  'violin'    => 'string'
}

puts instSection['oboe']

Instrukcje warunkowe, pętle

i = 0
while i < 10
  if i % 2 == 0
    puts '2'
  elsif i % 3 == 0
    puts '3'
  else
    puts 'n'
  end
  i+=1
end

Można też używać notacji odwrotnej:

puts "test" if true

i = 0
begin
  i+=1
  puts "test" 
end while i < 10

Symbole

Ruby zawiera obiekt nazwany symbolem. Symbol jest zawsze poprzedzony dwukropkiem, po utworzeniu symbolu używana jest jego jedna początkowa kopia. Symbol można konwertować do stringa i na odwrót.

Bloki

W ruby istnieje pojęcie bloku kodu który w rzeczywistości jest funkcją nienazwaną. Bloki mogą przyjmować argumenty, być przekazywane do funkcji. Istnieje specjalna funkcja yield która wykonuje przekazany do funkcji blok.

Blok definiuje się elementami do kod end lub też przez nawiasy klamrowe. Jeśli metoda przyjmuje blok najpierw należy przekazać w nawiasie () argumenty zwykłe a potem blok, tablice asocjacyjne trzeba przekazywać jako argumenty w nawiasach ().

a = %w{ ant bee cat dog elk }

#przekaż blok z argumentem
a.each do |word|
  puts word
end

#metoda each z Array mogła by być zaimplementowana następująco
def each
  for each element
    yield(element)  #sposób na przekazanie argumentu do wykonywanego bloku
  end
end

Definiowanie klas

Atrybuty i metody

Przez @nazwa oznacza się atrybuty instacji klasy, przez @@nazwa oznacza się atrybuty klasy. Nie trzeba wprost informować o istnieniu atrybutów, wystarczy że odwołania do nich pojawią się w kodzie metody.

Do klasy można się odwołać tylko przez metodę, nie ma czegoś takiego jak bezpośredni dostęp do atrybutu.


class Song
  @@instances = 0 #atrybut klasowy

  #inicjalizacja wywoływana automatycznie po utworzeniu obiektu metodą klasową new
  def initialize(name, artist, duration)
    @name     = name
    @artist   = artist
    @duration = duration

    @@instances += 1
  end

  def name
    @name
  end

  def artist
    @artist
  end

  def duration
    @duration
  end

  def name=(value)
    @name = value
  end

  def artist=(value)
    @artist = value
  end

  def duration=(value)
    @duration = value
  end
end

#z klasy można korzystać w następujący sposób:
aSong = Song.new("Bicylops", "Fleck", 260)
aSong.duration = 257 #wykorzystanie settera

aSong.duration #wykorzystanie gettera, zwróci 257

W kodzie powyżej często pojawia się ten sam schemat - gettery i settery - istnieją bardziej zwięzłe notacje:

class Song
  attr_reader :name, :artist, :duration  #tworzy gettery
  attr_writer :name, :artist, :duration  #tworzy settery

  def initialize(name, artist, duration)
    @name     = name
    @artist   = artist
    @duration = duration
  end
end

Można też użyć "attr" aby utworzyć getter oraz true lub false w zależności czy ma być utworzony setter:

class Song
  attr :name, true
  attr :artist, true
  attr :duration, true

  def initialize(name, artist, duration)
    @name     = name
    @artist   = artist
    @duration = duration
  end
end

Można rozbić definicje klasy - wtedy można korzytać tylko z metod które zostały już zdefiniowane:

class Song
  attr :name, true
  attr :artist, true

  def initialize(name, artist, duration)
    @name     = name
    @artist   = artist
    @duration = duration
  end
end

aSong = Song.new("Bicylops", "Fleck", 260)
aSong.name = "Aa"

class Song
  attr :duration, true
end

aSong.duration = 45

Domyślnie metody są publiczne, oprócz initialize która jest zawsze prywatna (wywołuje ją new). Zakres dostępności można ustawiać:

class Song
 protected
  attr :name, true
  attr :artist, true
 
 public
  attr :duration, true

  #metoda initialize pozostaje prywatna
  def initialize(name, artist, duration)
    @name     = name
    @artist   = artist
    @duration = duration
  end
end

lub inaczej:

class Song
  attr :name, true
  attr :artist, true
  attr :duration, true

  def initialize(name, artist, duration)
    @name     = name
    @artist   = artist
    @duration = duration
  end

  protected :name, :name=, :artist, :artist=
end
Dziedziczenie

Dziedziczenie oznacza się symbolem "<". Rozszerzmy klasę Song dodając atrybut lyrics:

class KaraokeSong < Song
  attr :lyrics, true

  def initialize(name, artist, duration, lyrics)
    super(name, artist, duration)
    @lyrics = lyrics
  end
end

Użyteczne linki

Rails

Instalacja

W labolatorium w systemie Linux nie trzeba niczego instalować.

  • Rails: Do instalacji najlepiej posłużyć się wspomnianym wcześniej narzędziem dla ruby: RubyGems. Wystarczy wydać komende gem install rails --include-dependencies
  • SQLite: Przykłady poniżej będą potrzebowały obsługi bazy danych SQLite, aby ją zainstalować wystarczy wydać komende gem install sqlite3-ruby, pakiet sqlite3-ruby umożliwia obsługę bazy SQLite
  • Rake: Program typu ant lub też make, instalacja poprzez gem install rake.

Szybki start

Główne cechy Rails zostaną przedstawione na przykładzie tworzenia prostego bloga.

Aby utworzyć nowy projekt wystarczy wydać polecenie:

rails blog

Gdzie blog to nazwa projektu, w katalogu bieżącym zostanie utworzony katalog blog wraz z wygenerowaną zawartością. Wśród wygenerowanych plików jest skrypt uruchamiający serwer:

cd blog
ruby script/server

Serwer uruchamia się domyślnie startując usługę http na porcie 3000, moża podać port wprost parametrem -p. Pod adresem http://localhost:3000/ powinna pojawić się strona startowa Rails.

Struktura wygenerowanego projektu

Rails jest frameworkiem opierającym się na architekturze MVC, czyli zakres odpowiedzialności zostaje rozdzielony w następujący sposób:

  • model - odpowiedzialny za zapis, odczyt danych
  • kontroler - odpowiedzialny za wykonywanie operacji (logiki biznesowej), przygotowanie widoku, odbieranie danych od widoku
  • widok - odpowiedzialny za prezentacje danych, pobranie danych od użytkownika

W związku z tym Rails odpowiednio generuje strukture projektu:

  • app/models - MVC: modele
  • app/controllers - MVC: kontrolery
  • app/views - MVC: widoki
  • app/helpers - MVC: funkcje pomocne dla widoków
  • db/migratinos - pliki migracji (generacja tabel w bazie danych)
  • config - pliki konfiguracyjne - routes.rb, database.yml
  • script - skrypty rails - generatory, obsługa serwera itp.
  • log - logi
  • public - grafiki, elementy html
  • public/javascript - pliki JS
  • test - miejsce na składowanie klas testujących
  • doc - dokumentacja generowana przez ri
  • vendor - katalog dla wtyczek i dodatków
  • tmp - katalog tymczasowy

Baza danych

Aby móc zapisywać modele musimy skonfigurować bazę danych, Rails potrafi obsługiwać następujące silniki baz danych:

  • MySQL
  • PostgreSQL
  • Oracle
  • Microsoft SQL Server
  • Sqlite (w wersji 2.x i 3.x)
  • IBM DB2
  • OpenBase
  • Sybase

Będziemy korzystać z SQLite 3 w tym celu należy wyedytować plik konfiguracyjny config/database.yml, powiniec wyglądać jak poniżej:

development:
  adapter: sqlite3
  encoding: utf8
  database: db/development.sqlite3
  pool: 5
  timeout: 5000

test:
  adapter: sqlite3
  encoding: utf8
  database: db/test.sqlite3
  pool: 5
  timeout: 5000

production:
  adapter: sqlite3
  encoding: utf8
  database: db/production.sqlite3
  pool: 5
  timeout: 5000

Zmienna RAILS_ENV ustawiana komendą rake ustala w jakim środowiku w danej chwili jesteśmy (domyślnie development).

Przepływa sterowania w RoR

Diagram prezentuje przepływ sterowania. Kolejne etapy wyglądają następująco:

Wstępna obsługa żądania

Serwer dostaje żądanie URL postaci [serwer][aplikacja]/[k1]/.../[kn]/item, czyli w takiej postaci jak by to było żądanie statycznego zasobu zagłębionego w n katalogach. Oczywiście nie będziemy rozkładać skryptów w ten sposób, żądanie takie kierowane jest do Dispatchera który zajmuje się przekształeceniem takiego żądania na konkretny kontroler i jego metodę (kontrolery są obiektami Ruby). Istnieje konwencja aby URL miał postać [serwer][aplikacja]/[kontroler]/[metoda] np: http://url.com/posts/new gdzie posts jest nazwą kodową kontrolera postów (w rzeczywistości nazywa się PostsController) a new jego metodą. Plik routes.rb definuje sposób przekształcania URL.

Etap kontrolera

Kontroler po obsłudze żądania (odpowiednia jego metoda została wywołana przez Dispatcher) może przekierować żądanie do innego punktu <kontroler,metoda> lub też przygotować dane do wyświetlenia w postaci swoich atrybutów i wyświetlić widok swój (przypisany krotce <kontroler,metoda>) lub inny.

Wyświetlenie odpowiedzi

Wybrany widok jest użyty do generacji odpowiedzi, korzysta z uprzednio przygotowanych przez kontroler atrybutów.


Generacja kontrolera, modelu i widoku

W Rails modele, widoki, kontrolery itd. generuje się za pomocą specjalnego skryptu "generate", skrypt potrafi generować każdy element z osobna jak i np. jednocześnie cały MVC dotyczący jakiegoś modelu (np. obługa postów).

Wygenerujemy model, kontroler i widok dla postów (post ma tytuł typu string i treść typu text):

ruby script/generate scaffold Post title:string body:text

Aby dokończyć CRUD dla postów musimy wygenerować tabelę w bazie danych (majac pliki ją generujące), wystarczy wydać polecenie:

rake db:migrate

Polecenie sprawdzi jakie nowe elementy się pojawiły i zaktualizuje strukture tabel. Możemy przystąpić do edycji postów, wystarczy wejść pod adres: http://localhost:3000/posts/

Chcielibyśmy też móc komentować posty, wygenerujemu więc też CRUD dla komentarzy który będziemy później intergrować z postami (komentarz powinien posiadać identyfikator posta do którego należy):

ruby script/generate scaffold Comment  post_id:integer body:text
rake db:migrate

Pod adresem http://localhost:3000/comments/ można zobaczyć CRUD dla komentarzy, umożliwia np. edycje identyfikatora postu.


Migracje

W katalogu db/migrate są pliki migracyjne dla odpowiednio modelu post i comment. Dla post plik ma postać:

class CreatePosts < ActiveRecord::Migration
  def self.up
    create_table :posts do |t|
      t.string :title
      t.text :body

      t.timestamps
    end
  end

  def self.down
    drop_table :posts
  end
end

Pliki migracyjne odpowiadają za tworzenie struktury bazy danych dla modeli. Każdy plik ma metodę UP wprowadzającą zmieną oraz DOWN wycofującą (w miare możliwości) zmianę.

Migracje można generować skryptem: ruby script/generate migration create_posts

Nazewnictwo

Warto zauważyć z tabela nazywa się posts a przecież tworząc CRUD dla postów użyliśmy słowa kodowego post: jest to związane z konwencją przyjętą w Rails - nazwy modeli piszemy w liczbie pojedyńczej a nazwy tabel (i migracji) ich dotyczących w liczbie mnogiej - Rails zrobił to automatycznie.

Indeksy

Rails nie dodaje sam indeksów, trzeba je dopisać ręcznie. Np. jeśli chcielibyśmy wyszukiwać posty po tytule należało by dopisać:

class CreatePosts < ActiveRecord::Migration
  def self.up
    create_table :posts do |t|
      t.string :title
      t.text :body

      t.timestamps
    end
    add_index :posts, [:title]
  end

  def self.down
    drop_table :posts
  end
end

Złączenia

Struktura złączeń (np. post_id w tabeli comments) jest określona przez konwencje - musimy się jej trzymać aby Rails odpowiednio wykorzystał do złączeń naszą strukture bazy. Jeśli odpowiednio zaadnotujemy modele Post i Comment Rails automatycznie wykorzysta pole post_id z tabeli comments - także nie możemy zmienić jego nazwy.

W przypadku złączeń wiele do wielu musielibyśmy przykładowo dodać tabele złączeniową o nazwie comments_posts z polami post_id i comment_id, uzupełnić jej zawartość, usunąć kolumne post_id z comments.

Wtedy migracje wyglądały by następująco:

ruby script/generate migration CreateCommentsPosts

Plik mógłby mieć postać:

class CreateCommentsPosts < ActiveRecord::Migration
  def self.up
    class Comment < ActiveRecord::Base
    end

    create_table :comments_posts, :id => null do |t|
      t.integer :comment_id
      t.integer :post_id
    end
    comments = Comment.find(:all)
    for comment in comments
      execute "insert into comments_posts values (#{comment.id}, #{comment.post_id})"
    end
    remove_column :comments, :post_id
  end

  def self.down
    .....
  end
end


Model

W katalogu app/models są wygenerowane modele. Każdy model reprezentuje pewną isntancję danych np. post lub komentarz. Nazwy modeli są zawsze w liczbie pojedyńczej, tabela odpowiadająca modelowi ma nazwę w liczbie mnogiej. Przy tworzeniu CRUD-a Rails automatycznie generuje migracje czyli i nazwy tabel np: post ==> posts, comment ==> comments ale też person ==> people.

W modelach nie określna się atrybutów (była by to redundacja informacji mając migracje).

Złączenia

Modele łączą się ze sobą na różny sposób, informacja o złączeniu pozwala:

  • mając zmienną np. typu Post pobrać listę komentarzy związanych z postem jeśli zadeklarujemy że post posiada komentarze
  • ułatwia dodawanie danych, odpowiednie identyfikatory w tabelach złączeniowych są uzupełniane automatycznie

Aby złączenia mogły funkcjonować muszą istnieć odpowiednio nazwane tabele, np. jeśli komentarz należy do posta to musi posiadać pole post_id, w przypadku złączenia wiele do wielu musie istnieć odpowiednia tabela złączeniowa.

Typy złączeń (każda ze stron złączenia musi określić swoją relację):

  • has_one <symbol w l. poj.>
  • belongs_to <symbol w l. pol.>
  • has_many <symbol w l. mn.>
  • has_and_belongs_to_many <symbol w l. mn.>

Strona zawierająca może dodatkowo dopisywać atrybuty przenoszenia akcji na element zawierany, np:

  •  :dependent => :destroy - przy usuwaniu zniszcz zawierane elementy
  •  :dependent => :nullify - przy usuwaniu ustaw zawieranym elementom klucz obcy na NULL

Więcej na temat złączeń: http://guides.rubyonrails.org/association_basics.html

Walidacja

W klasie modelu można dodawać linijki kodu nakazujące walidację obiektu, jest wiele typów walidacji, np:

  • validates_confirmation_of :email
  • validates_presence_of :symbol_atrybutu
  • validates_numericality_of :games_played, :only_integer => true
  • validates_length_of :password, :in => 6..20

Więcej na temat walidacji:

  • Ćwiczenie 1: Dodaj odpowiednie adnotacje do modelu Post i Comment (komentarz należy do postu, post ma wiele komentarzy, komentarze powinny być usunięte po usunięciu posta) oraz do modelu Post dodaj sprawdzanie istnienia tytułu i treści posta.

Rozwiązanie

Kontroler

Kontrolery są odpowiedzialne za:

  • logikę biznesową
  • przygotowywanie widoków (np. widok edycji przygotowywany przez metodę edit pobierającą obiekt do edycji z bazy i ustawiającą go na atrybucie)
  • odbieranie danych (np. metoda create odbiera obiekt i zapisuje do bazy danych)

Krotka <kontroler, metoda> ma przypisany widok, może przekierowywać żądanie do innej krotki lub wyświetlić widok (swój lub innej krotki). Kontrolery dziedziczą z klasy Application, tam można umieszcać wspólny kod.

W kontrolerach można definiować filtry (metody after_filter, before_filter) przed lub po metodzie, pierwszy argument to nazwa metody do wykonania, drugi to wykluczone (:except) lub jedyne wybrane metody (:only) (jest opcjonalny) np.

after_filter :loguj, :except => [:create, :new]

Czyli po wykonaniu metod oprócz create i new uruchom logowanie.

Można też zadeklarować skip_before_filter, zostanie uruchomiony przed wszystkimi innymi filtrami.

  • Ćwiczenie 2: W kontrolerze PostController do wszystkich metod oprócz index i show dodaj sprawdzanie czy użytkownik jest zalogowany a jeśli nie jest powienien wyświetlić się monit o podanie loginu i hasła administratora. Użyj metody authenticate_or_request_with_http_basic której należy przekazać blok przyjmujący parametr login, hasło i zwracający true lub false w zależności czy login, hasło są akceptowane, metoda sprawdza czy użytkownik jest zalogowany, jeśli nie to wyświetla monit.

Rozwiązanie

  • Ćwiczenie 3: W kontrolerze CommentController potrzebna nam będzie tylko metoda create, zmodyfikuj create tak aby poprawnie ustawiała komentarzowi id posta. Załóż że kontroler dostaje dodatkowy paramter post_id, na koniec kontroler powinien przekierować użytkownika do posta. Do utworzenie komentarza użyj metody Post.comments.create! <obiekt Comment> (możemy pobrać kolekcje komentarzy z posta). W jaki sposób pobrać post z bazy danych, argumenty z żądania oraz zrobić przekierowanie sprawdź w PostController - metoda update.

Rozwiązanie

Mapowanie

Plik config/routes.rb definuje jak zamieniać adresy URL na konkretne kontrolery, metody i ich argumenty. W wygenerowanym pliku są dwa automatycznie wygenerowane wpisy dotyczące zasobów:

map.resources :comments
map.resources :posts

Są to pewne skróty notacyjne, np. map.resources :photos jest równoważne wprowadzeniu reguł w tabeli.

oraz wpisy końcowe w przypadku kiedy żaden poprzedni nie działa:

map.connect ':controller/:action/:id'
map.connect ':controller/:action/:id.:format'

zachowujące konwencje określania kontrolera i metody.

Ponieważ chcemy aby komentarze należały do postów oraz żeby łatwo kojarzyć komentarz z postem zdefiniujemy dostęp do komentarzy za pomocą zagłębienia w poście do którego komentarz należy, np. komentarz 5 postu 2 miałby adres: /post/2/comment/5.

Wpisy dotyczące zasobów zamieniamy jednym wpisem:

map.resources :posts, :has_many => :comments

Jest to skrót notacyjny od:

map.resources :posts do |post|
  post.resources :comments
end

Dodatkowo możemy określic co ma być stroną startową aplikacji, dodajemy wpis:

map.root :controller => "posts", :action => "index"

Aby routing zadziałał trzeba usunąc plik public/index.html

Teraz przejście pod adres http://localhost:3000/ przeniesie nas do listy postów.

Polecenie rake routes wypisze wszyskie zdefiniowane przekierowania.

Widok

Widok odpowiedzialny za wyświetlanie danych oraz ich pobieranie od użytkownika jest wydzielony dla każdej akcji kontrolera, pliki o nazwie danej akcji zapisuje się w katalogu o nazwie kontrolera. Widok jest stroną html ze wstawkami Ruby: kod ruby umieszczamy w <% %>, natomiast w <%= %> umieszczamy wyrażenie którego wynik zostanie wstawiony w html.

Wyświetlanie widoku

Domyślnie po zakończeniu akcji Rails wyświetla widok przypisany do tej akcji (według kontrolera i nazwy akcji). Do zmiany domyślnego zachowania służą np.:

  • render :nothing => true - nie wyświetlaj nic
  • render  :controller => 'kontroler', :action =>'akcja' - renderuj widok akcji akcja kontrolera kontroler
  • render :text => "OK" - zwróć tekst
  • render :json => @product - zwróć w formacie JSON
  • redirect_to posts_path - przekieruj do indeksu postów

Widoki częściowe

Istnieje możliwość stworzenia fragmentu (partial) widoku i włączania go do innego widoku. Partiale umieszcza się w plikach zaczynających się od _ w nazwie, nazwa pliku (bez _ i rozszerzenia) to nazwa partiala.

Włączanie partiali:

  • Partiale nazwane można włączyć do widoku metodą render :partial => 'partial' aby włączyć wskazany _partial.html.erb.
  • Można również za pomocą komendy render :partial => @post renderować model, wtedy używany jest partial o nazwie _post.html.erb i dostaje on obiekt Post, jeśli podamy kolekcję można tak jak poniżej wyświetlić całą listę używając partiala dla pojedyńczego elementu.

Popraw widok postu tak aby wyświetlał komentarze oraz formularz dodawania nowego komentarza:

Plik app/views/posts/show.html.erb powinien wyglądać następująco:

<p>
  <b>Title:</b>
  <%=h @post.title %>
</p>

<p>
  <b>Body:</b>
  <%=h @post.body %>
</p>

<p>
  <%= link_to 'Edit', edit_post_path(@post) %> |
  <%= link_to 'See All Posts', posts_path %>
</p>

<h2>Comments</h2>
<div id="comments">
  <%= render :partial => @post.comments %>
</div>

<% form_for [@post, Comment.new] do |f| %>
  <p>
    <%= f.label :body, "New Comment"%><br/>
    <%= f.text_area :body %>
  </p>
  <p><%= f.submit "Add comment" %></p>
<% end %>

Plik ten odwołuje się do widoku częściowego, należy go utworzyć:

Plik app/views/comments/_comment.html.erb powinien wyglądać następująco:

<% div_for comment do %>
  <p>
    <strong>Posted <%= time_ago_in_words(comment.created_at) %></strong>
    <br/>
    <%= h(comment.body) %>
  </p>
<% end %>


Szablony

Szablony pozwalają wpisać w jeden plik elementy pojawiające się na wielu stronach. Domyślnie oddzielny szablom jest tworzony dla każdego kontrolera (w katalogu app/views/layouts) wystarczy go skasować aby wykorzystywany był ogólny szablon application.html.erb.

W szablonach wstawiamy metodę yield która jest potem podmieniana widokiem, więcej informacji o szablonach.

Do metody render można dodać nazwe szablonu jaki zostanie użyty - argument :layout.


Ajax

Rails ma wbudowane wsparcie dla Ajax, wystarczy zdefiniować do której akcji, kontrolera ma się odwołąc element na stronie, zdefiniować który element ma zostać zaktualizowany oraz oprogramowac odpowienią akcję w kontrolerze.

Prosty przykład

Zmienimy dodawanie komentarzy na zdarzenie w JavaScript, tak aby dodanie komentarza nie przeładowywało strony.

Należy dodać deklaracje biblioteki JS do pliku app/views/layouts/posts.html.erb (w elemencie head):

<%= javascript_include_tag :all %>

W pliku app/views/posts/show.html.erb formularz form_for zmieniamy na remote_form_for (przy wyłączonym JS działa jak form_for, przy włączonym zamiast przeładowywać stronę wysyła zpaytanie do serwera).

Zmieniamy CommentsController na:

class CommentsController < ApplicationController
  def create
    @post = Post.find(params[:post_id])
    @comment = @post.comments.create!(params[:comment])
    respond_to do |format|
      format.html { redirect_to @post }	
      format.js
    end 
  end
end

Kontroler inaczej reaguje na zapytanie JS - przechodzi do domyślnego widoku.

Dodajemy domyślny widok dla metody create w formacie JS który zostanie pobrany i uruchomiony na stronie pytającej:

Plik: app/views/comments/create.js.rjs.

page.insert_html :bottom, :comments, :partial => @comment
page[@comment].visual_effect :highlight
page[:new_comment].reset

Typowe narzędzia

  • <%= link_to_remote "Update time", { :update => "time", :url => { :action => :now, :arg1 => var1, :arg2 => var2 } } %> - link odpytujący akcję now o nową zawartość elementu o id=time (aktualny czas). Wystarczy napisać akcję zwracającą string
  • <% form_remote_tag :url %> - podobnie jak link_to_remote z tym że przesyłana jest zawartość formularza
  • <%= observe_field('field_id', :update => 'id elementu', :controller => 'kontroler', :action => 'akcja') %> - w razie zmiany pola wykonaj akcję

Sprawdzenie umiejętności

  • Ćwiczenie 4: Dodaj możliwość oceniania postu z wyświetlaniem aktualnej liczby punktów, ocenianie nie powinno przeładowywać strony przy włączonym Java Script, rozszerzenie: powinno działać przy wyłączonym Java Script. Kolumna w tabeli posts niech nazywa się rank, do kontrolera powinny być przekazane 2 parametry: post_id z identyfikatorem posta oraz vote o wartości +1/-1. Ewentualny routing w wersji rozszerzonej niech nazywa się rank.

Na stronie http://api.rubyonrails.org/ są opisy poszczególnych metod itp.

Baza danych

Wskazówka

Rozwiązanie

Kontroler

Wskazówka

Wersja podstawowa Rozwiązanie

Wersja rozszerzona Rozwiązanie

Widok

Wskazówka

Wersja podstawowa: Rozwiązanie

Wersja rozszerzona: Rozwiązanie

Gotowy blog

blog.zip

Użyteczne linki

Osobiste