Tutorial 3 - Newsy i komentarze

Tutorial ten pokazuje jak używać i korzystać z relacji w bazie danych. Pokazuje także szersze wykorzystanie części (:partial) oraz modeli. Oczywiście przydadzą się do tego umiejętności nabyte z poprzednich tutoriali.

Nasza trzecia aplikacja webowa będzie systemem newsów z komentarzami. Na stronie umieszczamy co jakiś czas nowe informacje (newsy), a nasi czytelnicy mogą je komentować. Naszym celem jest stworzenie aplikacji, która wspomoże proces tworzenia nowych newsów, jak i pozwoli czytelnikom na łatwe dodawanie swoich komentarzy.

Krok 1: Tworzymy projekt z bazą danych

Tak jak w poprzednich tutorialach na początku tworzymy nowy projekt o nazwie projekt3 oraz tworzymy dla niego bazę danych o nazwie projekt3_development.

Krok 2: Migrujemy newsy

Tworzymy migrację (zakładka Generators, wybierz migration) o nazwie stworz_news.

Gdy zostanie utworzona, edytujemy plik db/migrate/001_stworz_news.rb, aby wyglądał jak na listingu poniżej:

class StworzNews < ActiveRecord::Migration   def self.up     create_table :newsy do |t|     t.column :tytul, :string     t.column :tresc, :text     t.column :data, :datetime     end   end   def self.down     drop_table :newsy   end end

Gdy utworzyliśmy plik migracji możemy ją wykonać (zakładka Rake Tasks, wybierz db:migrate).

Krok 3: Szkielet dla newsów

Zanim utworzymy szkielet obsługi naszych newsów, musimy jak w poprzednich tutorialach w pliku config/enviroment.rb utworzyć regułę, mówiącą, że liczba mnoga od news to newsy i od razu na przyszłość komentarz to komentarze:

inflect.irregular'news', 'newsy' inflect.irregular'komentarz', 'komentarze'

oraz ustawić kodowanie (klikamy dwa razy na projekcie, z menu kontekstowego Properties, w oknie Properties for projekt2 ustawiamy Text file encoding na UTF-8).

Teraz możemy przystąpić do tworzenia szkieletu. Zatem generujemy szkielet obsługi newsa (zakładka Generators, wybieramy scaffold, a w polu tekstowym wpisujemy nazwę: news).

Krok 4: Uruchamiamy serwer

Uruchom serwer (zakładka Servers, kliknij Start). W dowolnej przeglądarce internetowej wpisz adres: http://localhost:3003/newsy/,  następnie kliknij New news. Jak widać mamy pole wyboru daty wraz czasem, więc tak jak w poprzednim tutorialu (krok 11) usuńmy je i dołączmy automatyczne dodawanie czasu do nowo tworzonych newsów. W tym celu w kontrolerze app/newsy_controller.rb w akcji create dodajemy kod jak na listingu:

  def create     @news = News.new(params[:news]) @news.data = Time.now     if @news.save     flash[:notice] = 'News was successfully created.' redirect_to :action => 'list'     else       render :action => 'new'     end   end

Krok 5: Wyświetlamy przez części (:partial)

Jednym z głównych założeń Ruby on Rails jest reguła DRY (ang. Don't Repeat Yourself) czyli po polsku NPS (Nie Powtarzaj Się). Ponieważ kod służący do wyświetlania pojedynczego newsa będzie nam potrzebny zarówno przy pojedynczym wyświetlaniu, jak i przy wyświetlaniu całej listy, możemy użyć części (:partial) do wyświetlenia pojedynczego newsa. Tworzymy zatem plik _news.rhtml w katalogu app/views/newsy/ następującej treści:

<div class="news">      <h2><%= link_to news.tytul, :action => 'show', :id => news %></h2>     <p><%=h news.tresc %></p>     <h4>     <%=h news.data.to_s(:short) %>     (<%= link_to 'Edit', :action => 'edit', :id => news %>)     (<%= link_to 'Destroy', { :action => 'destroy', :id => news }, :confirm => 'Are you sure?', :post => true %>)     </h4> </div>

Jeżeli nie wiesz, jak interpretować niektóre wiersze powyższego kodu - nie przejmuj się, wyjaœnimy to później.
Teraz wystarczy dodać wyświetlanie tej części (render :partial) do widoków list i show. Edytujemy zatem plik app/views/newsy/list.rhtml tak, by wyglądał jak poniżej:

<h1>Newsy naszej strony:</h1> <%= render :partial => "news", :collection=> @newsy.reverse %> <%= link_to 'Nowy news', :action => 'new' %>

A także plik app/views/newsy/show.rhtml, by wyglądał następująco:

<%= render :partial=> "news", :object => @news %> <%= link_to 'Powrót', :action=> 'list' %>

Dodajmy teraz jakiegoś newsa (w dowolnej przeglądarce internetowej wpisz adres: http://localhost:3003/newsy/,  kliknij Nowy news, wypełnij formularz, a następnie kliknij Create).

Krok 6: Tworzymy model komentarza

Jak już mówiliśmy w tutorialu drugim, modele reprezentują tabele w bazie danych. Ponieważ do naszych newsów potrzebujemy dołączyć komentarze, będziemy zatem potrzebowali tabeli komentarzy w bazie danych, więc u nas modelu komentarza. Dotychczas tabelę w bazie tworzyliśmy generując migrację. Tworząc model utworzymy zarówno model jak i migrację. Dwa w jednym.

W zakładce Generators, wybieramy model i wpisujemy nazwę komentarz, a następnie klikamy Go. Uzupełniamy następująco powstałą migrację db/migrate/002_create_komentarze.rb

class CreateKomentarze < ActiveRecord::Migration   def self.up     create_table :komentarze do |t|       t.column :tresc, :text       t.column :podpis, :string       t.column :data, :datetime       t.column :news_id, :integer, :foreign_key => :newsy     end   end   def self.down     drop_table :komentarze   end end

Krok 7: Edytujemy model

Obecnie w naszej aplikacji mamy 2 modele, reprezentują one tabele: newsy i komentarze. Połączmy relacją te dwie tabele. Na relacyjnym diagramie mogłoby to wyglądać następująco:

Jak widzimy newsy posiadają wiele komentarzy (has_many). Natomiast każdy komentarz należy do konkretnego jednego newsa (belogns_to). Zapiszmy te informacje w modelach. W modelu app/models/news.rb dopisujemy jeden wiersz:

class News < ActiveRecord::Base   has_many :komentarz end

zaś w modelu app/models/komentarz.rb dopisujemy także jeden wiersz:

class Komentarz < ActiveRecord::Base   belongs_to :news end

Krok 8: Wykonujemy migrację

Ponieważ w kroku 6 utworzyliśmy nowy plik migracji, musimy ją wykonać (zakładka Rake Tasks, wybierz db:migrate).

Krok 9: Tworzymy formularz komentarza

Stworzyliśmy model, ale wciąż nigdzie nie wyświetlamy komentarzy i nie mamy formularza do ich dodawania. Teoretycznie mogliśmy wygenerować szkielet dla nich zamiast modelu. No to dopiero teraz o tym mówisz? No, ale... szkielet szkieletem, a przydałaby się także wiedza o tworzeniu formularzy, choćby po to, by przy trudniejszych projektach móc coś zmienić ręcznie i wiedzieć gdzie to zrobić. Także pomimo wszechogarniającego lenistwa spróbujmy napisać kilka linijek kodu. Pamiętacie widok show.rhtml? Dodamy do niego wyświetlanie komentarzy i formularz do ich dodawania. Edytuj plik app/views/newsy/show.rhtml, zgodnie z poniższym listingiem.

<%= render :partial => "news", :object => @news %> <%= link_to 'Powrót', :action => 'list' %> <% for komentarz in @news.komentarz %> <p><b><%= h komentarz.podpis %>:</b> <%= h komentarz.tresc %></p><hr /> <% end %> <%= start_form_tag :action => 'komentarz', :id => @news %> <p><label for="komentarz_tresc">Tresc</label><br/> <%= text_area 'komentarz', 'tresc', :cols => '25', :rows=>'5' %></p> <p><label for="komentarz_podpis">Podpis</label><br/> <%= text_field 'komentarz', 'podpis',:size => '15' %></p> <%= submit_tag "Dodaj" %> <%= end_form_tag %>

I co my tu mamy nowego... Pętlę for powinniście pamiętać jeszcze z poprzedniego tutorialu (krok 12). Wtedy jednak iterowaliśmy po wpisach (zmienna @wpisy). Tym razem iterujemy po komentarzach, a ponieważ dany komentarz ma być związany z wyświetlanym newsem, to poprzez zmienną @news odwołujemy się do jego komentarzy. Zatem iterujemy pętlę po @news.komentarz.

Kolejne wiersze tworzą nam formularz. Jak nie trudno się domyślić formularz w Ruby on Rails tworzymy znacznikami <%= start_form_tag %> i <%= end_form_tag %>. Znacznik start_form_tag zawiera ponadto informacje jaka akcja ma zostać wywołana po wysłaniu formularza oraz z którym newsem ma być związany ten komentarz - oczywiscie aktualnie wyświetlanym (:action => 'komentarz', :id => @news).

Tagi text_field i text_area wewnątrz formularza tworzą nam jego pola do wypełniania. Pierwsze dwa tworzą tag HTML textarea z określonymi parametrami - ilość kolumn :cols i ilość wierszy :rows. Następne tworzy tag input o rozmiarze określonym parametrem :size. Oba tagi text_area i text_field jako pierwszy parametr przyjmują nazwę tablicy asocjacyjnej. Czym dokładnie jest tablica asocjacyjna, przez niektórych nazywana słownikiem długo by trzeba wyjaśniać. Nam wystarczy informacja, że jest to taka specyficzna tablica do której elementów możemy się odwoływać przy pomocy unikalnych kluczy, a nie jak zazwyczaj w tablicy po numerach. Zatem informacja z pola podpis: <%= text_field 'komentarz', 'podpis' %> zostanie zapisana w komentarz[podpis]. Zatem klucz w tej tablicy określa drugi parametr tagu.

Ostatni wiersz <%= submit_tag "Dodaj" %> tworzy przycisk służący do zatwierdzania formularza z tekstem "Dodaj".

Krok 10: Tworzymy akcję komentowania

Mamy już formularz, ale przesyła on informację do akcji komentarz z parametrem :id. Tymczasem takiej akcji w kontrolerze nie mamy. Napiszmy ją. Otwieramy plik app/newsy_controller.rb i przed ostatnim end wpisujemy:

  def komentarz     @news = News.find(params[:id])     @news.komentarz.create(params[:komentarz])     flash[:notice] = "Komentarz został dodany"     redirect_to :action => 'show', :id => @news   end

W pierwszym wierszu akcji komentarz wyszukujemy naszego newsa po parametrze :id przesłanym przez formularz. Potem poleceniem create tworzymy nowy komentarz przekazując w parametrze dane z formularza. Jak widzicie, mimo że komentarz składa się z pola treść i podpis, przekazujemy tylko jeden parametr, czyli omówioną w poprzednim kroku tablicę asocjacyjną komentarz. Ostatni wiersz przekierowuje nas po dodaniu komentarza z powrotem na stronę wyświetlającą newsa, którego komentowaliśmy.

Pora obejrzeć nasze komentarze w akcji. Wpisujemy adres: http://localhost:3003/newsy/ i klikamy na tytuł utworzonego wcześniej newsa.

Przeniosło nas do widoku akcji show. Jak widać pojawił się formularz dla komentarzy. Dodaj kilka komentarzy od siebie, by sprawdzić, czy formularz działa jak należy.

Teraz wróć do akcji list klikając "Powrót" i dodaj nowego newsa oraz jakiś komentarz dla niego.

Krok 11: Ty pie*** ku***!

No właśnie - co zrobisz, gdy ktoś doda taki komentarz? Jeśli będzie "tylko" wulgarny (np. spieprzaj dziadu) to da się przeżyć, ale wyobraź sobie, że ktoś w twoich komentarzach obrazi prezydenta (sic!). Skoro z art. 135. §2 Kodeksu Karnego ścigano już emeryta za rozsyłanie rysunku kaczek, bezdomnego, który po pijaku nawrzucał prezydentowi (i nie są to bynajmniej dziennikarskie kaczki!), to dlaczego nie mieliby ścigać ciebie - administratora newsów i komentarzy. ;)

Sami widzicie, że trzeba taki komentarz czym prędzej usunąć - dla własnego dobra. Zatem zrobimy do tego specjalną stronę, która wyświetli nam wszystkie komentarze i da możliwość ich usuwania.

W tym celu potrzebujemy w kontrolerze przygotowac 2 akcje - jedna pobierająca wszystkie komentarze (byśmy je potem mogli wyświetlić) i druga usuwająca wybrany przez nas komentarz. Jak w poprzednim kroku otwieramy plik app/newsy_controller.rb i przed ostatnim end wpisujemy:

  def komentarze     @komentarze = Komentarz.find(:all)   end   def usun_komentarz     Komentarz.find(params[:id]).destroy     redirect_to :action => 'komentarze'   end

Akcja komentarze pobiera do zmiennej @komentarze wszystkie zapisane w bazie komentarze. Natomiast akcja usun_komentarz jak nie trudno się domyślić usuwa zadany parametrem :id komentarz. Do usunięcia została użyta komenda destroy.

Z akcją pobierającą wszystkie komentarze związany będzie także widok, bo przecież chcemy je wyświetlić i obejrzeć, a nie w ciemno usuwać. Tworzymy zatem w katalogu app/views/newsy/ plik komentarze.rhtml o następującej treści:

<h1>Spis komentarzy:</h1> <table border="1" cellpadding="0" cellspacing="0"> <% for komentarz in @komentarze %> <tr>     <td><b><%=h komentarz.podpis %></b>:     <%=h komentarz.tresc %></td>     <td><%= link_to 'Usuń', { :action => 'usun_komentarz',     :id => komentarz }, :confirm => 'Are you sure?',     :post => true %></td> </tr> <% end %> </table>

Jedyną nowością może tu być dość długi wiersz: <%= link_to 'Usuń', { :action => 'usun_komentarz', :id => komentarz }, :confirm => 'Are you sure?', :post => true %>. Tworzy on link o treści "Usuń", który wywołuje zdefiniowaną przez nas akcję usun_komentarz z parametrem :id. Podaliśmy także 2 inne parametry: :confirm to treść komunikatu jaki się pojawi w okienku potwierdzającym usuwanie komentarza, natomiast :post to techniczny parametr mówiący o wyborze metody POST do przesyłania danych.

Pora sprawdzić, czy wszystko działa. Wpisujemy adres: http://localhost:3003/newsy/komentarze i powinniśmy otrzymać spis wszystkich komentarzy.

Krok 12: A może to nasz news trzeba usunąć?

A może to nie komentarze są złe, tylko news, który je sprowokował? Niezależnie od tego jak było, czasem zachodzi jednak potrzeba usunięcia newsa. Dodaj jakiś news i komentarze do niego, a następnie usuń go. Czy zauważyliście coś niepokojącego? Nawet jeśli news został usunięty - jego komentarze pozostają.

Aby wraz z usuwaniem newsa usunąć wszystkie jego komentarze wystarczy w modelu newsa obok has_many dopisać 2 słowa jak poniżej:

class News < ActiveRecord::Base   has_many :komentarz, :dependent=> :destroy end

Krok 13: Pstrokate dodatki...

Nasze newsy wraz z komentarzami działają, jednak praktycznie byłoby dodać jakieś style css, stronowanie oraz walidację pól formularza, czyli dokładnie to, co poznaliście w drugim tutorialu. 

Oprócz tego, możliwość usuwania komentarzy oraz edytowania i usuwania newsów powinien mieć tylko zalogowany administrator, ale o tym w kolejnych tutorialach...

Koniec i bomba, a kto czytał ten trąba.