Ruby on Rails: Kniha hostů a blog

25. 11. 2005
Doba čtení: 11 minut

Sdílet

Dnes vytvoříme knihu hostů a blog. Mimo jiné začneme pracovat s modelem, konzolí a vysvětlíme si pravidla pojmenování tabulek, souborů a tříd. Prostě nuda s jediným screenshotem.

Slíbil jsem knihu hostů, a po vzoru PR videí se do ní pustíme hned teď. Budeme používat mysql databázi, ačkoli Rails umějí pracovat nad různými databázemi. Mysql je jakýmsi „standardem“. Osobně preferuji sqlite, jenže ovladač není součástí běžné distribuce Ruby, naproti tomu ovladač mysql obsahují přímo Rails (navíc je napsán v Ruby, takže by neměl dělat problémy).

Kniha hostů

Vytvoření databáze

Ještě nemáme databázi projektu Rolldance, jejíž součástí bude, mimo jiné, tabulka guestbooks. Napravíme to. Můžete použít phpmyadmin, nebo jako já konzoli mysql, přičemž všechny dotazy budu předávat na standardní vstup této konzole.

1. nejprve databáze

mig> echo "create database rolldance_development" | mysql -u root --password=abcd 

2. a tabulka

soubor guestbooks.sql:

create table guestbooks (
    id int unsigned auto_increment,
    nick varchar(30),
    date datetime,
    message text,
    primary key (id)
) 
mig> cat guestbooks.sql | mysql -u root --password=abcd rolldance_development 

Konfigurace Rails

Předpokládám, že máte databázi rolldance_deve­lopment připravenou. Musíme Rails nakonfigurovat, aby ji používaly. Editujte soubor config/databa­se.yml, sekci development. Soubor je ve formátu YAML, takže je žádoucí dodržovat odsazení zleva. V souboru jsou defaultně uvedeny i příklady pro použití jiných databází než mysql, moje konfigurace vypadá takto:

development:
  adapter: mysql
  database: rolldance_development
  username: root
  password: abcd
  socket: /tmp/mysql.sock 

tip: YAML formát je opravdu jednodušší než konfigurace v XML. Zkuste si spustit irb, interaktivní Ruby konzoli, a načíst konfigurační soubor.

mig> irb
irb(main):001:0> require 'yaml'
=> true
irb(main):001:0> YAML::load( File.open( 'database.yml' ) ) 

Jednoduché, že? V YAML lze snadno řešit například lokalizační soubory.

Nastavení raději otestujeme, protože s mysql 4.0.? jsem měl mírné potíže. Spusťte konzoli Rails a napište do ní „ActiveRecord::Ba­se.connection“. Ke konzoli samé se ještě vrátím, nyní je nutné, aby to, co uvidíte, vypadalo takto:

=> #<ActiveRecord::ConnectionAdapters::MysqlAdapter:0x40857e74 @connection_options=["localhost", "root", "gggggggg", "iliketraffic2", nil, nil], @runtime=0, >
... 

Konzoli ukončete ctrl-d.

Scaffold generátor

Jak jsem již zmínil, nyní použijeme scaffold generátor k sestavení celé funkční webové aplikace (kontroléru, modelu a pohledů). Nevěříte? Funguje na principu CRUD (Create Read Update Delete), což znamená… a dosti bylo řečí, zkusme si ho v praxi.

mig> script/generate scaffold Guestbook Guestbook
      exists  app/controllers/
      exists  app/helpers/
      create  app/views/guestbooks
      exists  test/functional/
... 

Končí-li vám výpis chybou

 error  Before updating scaffolding from new DB schema, try creating a table for your model (Guestbook) 

tabulku guestbooks jste zřejmě pojmenovali jen guestbook… ? Z jakého důvodu vyžaduje scaffold množné číslo? Magie. Ostatně z podobných důvodů je Guestbook na řádku generátoru specifikováno dvakrát. Objasním později.

Teď spusťte WEBrick server

mig> script/server 

a v prohlížeči zadejte adresu http://localhos­t:3000/guestbo­ok

Rails 3

Zkuste naklikat pár záznamů. Všímejte si přitom, jak se mění url.

Hezké, ale co dál?

Psal jsem v prvním díle, že scaffolding je způsob, kterým lze rychle vygenerovat prototyp aplikace. Na výsledných stránkách takovou knihu hostů nikdo používat nebude, ale potřebujeme-li základní strukturu, není třeba psát vše ručně. Stačí vygenerovat kostru a později doladit detaily.

Co generátor vygeneroval?

Nemáte-li k dispozici výpis, vzniknuvší při generování knihy hostů, zkuste klidně vygenerovanou strukturu odstranit a vygenerovat ji znovu.

mig> script/destroy scaffold Guestbook Guestbook 

Ze záznamu zřejmě scaffold vyrobil kontrolér v app/controller­s/guestbook_con­troller.rb, několik .rhtml souborů v adresáři app/views/, jejichž názvy odpovídají názvům akcí kontroléru a model v app/models/gu­estbook.rb.

Kontrolér

Nejprve se podívejme do kontroléru. Připomínám, že proměnné se zavináčem na začátku značí proměnné objektu (jsou sdíleny všemi metodami), a jsou také dostupné v šabloně pohledu. Tyto proměnné představují „uvařená data“.

app/controller­s/guestbook_con­troller.rb

class GuestbookController < ApplicationController
  def index
    list
    render :action => 'list'
  end

  def list
    @guestbook_pages, @guestbooks = paginate :guestbooks, :per_page => 10
  end

  def show
    @guestbook = Guestbook.find(params[:id])
  end

  def new
    @guestbook = Guestbook.new
  end

  def create
    @guestbook = Guestbook.new(params[:guestbook])
    if @guestbook.save
      flash[:notice] = 'Guestbook was successfully created.'
      redirect_to :action => 'list'
    else
      render :action => 'new'
    end
  end

  def edit
    @guestbook = Guestbook.find(params[:id])
  end

  def update
    @guestbook = Guestbook.find(params[:id])
    if @guestbook.update_attributes(params[:guestbook])
      flash[:notice] = 'Guestbook was successfully updated.'
      redirect_to :action => 'show', :id => @guestbook
    else
      render :action => 'edit'
    end
  end

  def destroy
    Guestbook.find(params[:id]).destroy
    redirect_to :action => 'list'
  end
end 

Princip naší guestbook je následující: Akce new zobrazí formulář, jímž zadáme nový záznam do knihy hostů. Jakmile formulář potvrdíme, akce create zkontroluje zadaná data a přidá záznam do databáze. Seznam všech záznamů pak zobrazíme akcí list. Akce show ukáže jeden konkrétní záznam, akce edit ho načte do formuláře jako při jejím vytváření (předvyplní formulář) a update po odeslání formuláře uloží změny. Destroy konečně odstraní záznam z databáze.

Je vidět, že kontrolér zodpovídá za celou knihu hostů.

Defaultní akce index se chová jako akce list. Pokud totiž nespecifikujeme na konci metod pohled (render), bude použit pohled defaultní, jenž vychází z názvu akce. Proto většina akcí nemá na konci render, byl by zbytečný. Tímto způsobem je možné například „namíchat“ data z více akcí a pak je zobrazit odlišným pohledem.

...
  def index
    list
    render :action => 'list'
  end
... 

Jednotlivé akce se nalézají na těchto adresách:

http://localhos­t:3000/guestbo­ok (= http://localhos­t:3000/guestbo­ok/index)
http://localhos­t:3000/guestbo­ok/new
http://localhos­t:3000/guestbo­ok/create
http://localhos­t:3000/guestbo­ok/list
http://localhos­t:3000/guestbo­ok/show
http://localhos­t:3000/guestbo­ok/edit
http://localhos­t:3000/guestbo­ok/update
http://localhos­t:3000/guestbo­ok/destroy

Všimněte si, že některé akce mají a jiné akce nemají odpovídající pohledy. Například akce create, která přidává položku do databáze, sama o sobě nepotřebuje žádný výstup. V případě chyby znovu zobrazí pohled new a v případě úspěchu přesměruje prohlížeč na akci list.

...
    if @guestbook.save
      redirect_to :action => 'list'
    else
      render :action => 'new'
    end
... 

Model

app/models/gu­estbook.rb

class Guestbook < ActiveRecord::Base
end 

Jmenuje se Guestbook a defaultně je takto prázdný. Přesto díky zdědění třídy ActiveRecord::Base toho umí spoustu (ActiveRecord obaluje databázi objektovým kódem). Nechme proto model nedotčen.

Model tvoří (při použití v kontroléru) rozhraní mezi kontrolérem a databází. Pokud vyextrahujeme čistou funkčnost, budou následující zápisy odpovídat databázovým dotazům. (V mém případě je v tabulce guestbook právě jeden záznam, který jsem naklikal přes web a má id rovno jedné, takže další záznam dostane id 2.)

gb1 = Guestbook.new( 'nick' => 'tom', 'message'=>'pekne blba guestbook teda' )
gb1.save 

INSERT INTO guestbooks( nick, messages ) VALUES ( ‚tom‘, ‚pekne blba guestbook teda‘ )

gb2 = Guestbook.find( 2 ) 

SELECT * FROM guestbooks WHERE id = 2

gb2.message = 'bude lepsi'
gb2.save 

UPDATE guestbooks SET message = ‚bude lepsi‘ WHERE id = 2

gb2.destroy 

DELETE FROM guestbooks WHERE id = 2

Kdo neuvidí, neuvěří. Zkuste si to! Opět spusťte interaktivní konzoli

mig> script/console
Loading development environment.
>> gb1 = Guestbook.new( 'nick' => 'tom', 'message'=>'pekne blba guestbook teda' )
=> #<Guestbook:0x40870190 @new_record=true, @attributes={"message"=>"pekne blba guestbook teda", "date"=>nil, "nick"=>"tom"}>
>> gb1.save
=> true
>> gb1.id
=> 2
>> gb2 = Guestbook.find( 2 )
=> #<Guestbook:0x40a867f4 @attributes={"message"=>"pekne blba guestbook teda", "date"=>nil, "nick"=>"tom", "id"=>"2"}>
>> gb2.nick
=> "tom" 

Předesílám, že ActiveRecord umí daleko více, než jen editovat tabulku. Řeší závislosti mezi více tabulkami, transakce, a též vytváří stromové struktury. Vzhledem k tomu, že výsledná kniha hostů by měla umět více úrovní záznamů, graficky znázorněno – odpověď bude více odsazena zleva, nejspíš využijeme právě stromové struktury.

Zatím si však vystačíme se stávající knihou hostů, a vlastně i blogem. Princip blogu je obdobný, nakonec i jednodušší než budoucí kniha hostů – takto jednoduchý již zůstane, žádné úrovně odpovědí mít nebude, pouze jeho editace bude chráněna heslem a dostupná v administračním rozhraní.

Pohled

Asi nejzajímavější vlastností scaffold generátoru je předgenerování formulářových prvků – helperů – na základě typů sloupců tabulky. Právě proto tabulka musí existovat předtím, než použijeme scaffold generátor. Formulář je sdílen pohledy new a edit, proto má soubor podtržítko na začátku a je includován v new.rhtml a edit.rhtml.

app/views/gues­tbook/_form.rhtml

<%= error_messages_for 'guestbook' %>

<!--[form:guestbook]-->
<p><label for="guestbook_nick">Nick</label><br/>

<%= text_field 'guestbook', 'nick'  %></p>

<p><label for="guestbook_date">Date</label><br/>
<%= datetime_select 'guestbook', 'date'  %></p>

<p><label for="guestbook_message">Message</label><br/>

<%= text_area 'guestbook', 'message'  %></p>
<!--[eoform:guestbook]--> 

Vytvoření blogu a menu

Blog vytvoříme skoro stejným způsobem jako knihu hostů.

soubor blogs.sql

create table blogs (
    id int unsigned auto_increment,
    blogger varchar(30),
    date datetime,
    message text,
    primary key (id)
) 
mig> cat blogs.sql | mysql -u root --password=abcd rolldance_development
mig> script/generate scaffold Blog 

http://localhos­t:3000/blogs

Pozor, narozdíl od Guestbook uveďte jen jedno slovo Blog! . Prostě magie. Navíc uděláme společný layout a v něm jednoduché menu, abychom mohli mezi blogem a guestbook přepínat. Jeden ze souborů v app/views/la­youts/ přejmenujte na rolldance.rhtml a druhý smažte.

app/views/lay­outs/rolldance­.rhtml

<html>
<head>
  <title>Rolldance: <%= controller.action_name %></title>
  <%= stylesheet_link_tag 'scaffold' %>
</head>
<body>

<h2>menu</h2>
<ul>
        <li><%= link_to 'Kniha hostů', :controller=>'guestbook' %></li>
        <li><%= link_to 'Blog', :controller=>'blogs' %></li>

</ul>

<p style="color: green"><%= flash[:notice] %></p>

<%= @content_for_layout %>

</body>
</html> 

Ještě je nutné upravit oba kontroléry, aby používaly stejný layout. Přidáme definici layoutu úplně nahoru:

class GuestbookController < ApplicationController

  layout 'rolldance'

  def index
    list
... 

Hned jsou naše stránky dynamičtější! No, chápu, z hlediska uživatele žádná bomba, ale proč hned používat biologické zbraně, že…

Pravidla pojmenování

pluralizace

Velkým kamenem úrazu a častým tématem mnoha diskuzí je pluralizace. Rails používají pojmenování tu v množném čísle, tam zase v jednotném, aby se v tom čert vyznal!

Určitě jste si všimli, že je název kontroléru knihy hostů v jednotném čísle, blogu v množném, obě tabulky jsou také v množném a oba modely v jednotném.

K čemu pluralizace je? Stejně se ptá spousta lidí v konferenci. Rails se především snaží napodobit mluvenou řeč, aby byly kódy čtivé. Opak nedělají, protože kdyby lidé začali mluvit v Ruby, svět by byl teprve naruby. Snad vám pomůže těchto 10 důvodů pluralizace.

Z vlastních zkušeností dodávám, že si zvyknout lze, platí totiž určitá pravidla. Podívejme se tedy, jak to s těmi pravidly je.

Ačkoli se kontrolér jmenuje GuestbookContro­ller, tabulka je pojmenována guestbooks. Ostatně, kontrolér by byl také GuestbooksCon­troller, pokud bychom nespecifikovali jiné jméno kontroléru, proto to druhé Guestbook.

mig> script/generate scaffold Guestbook Guestbook 

Připadalo mi divné kontrolér jedné knihy hostů pojmenovat Guestbooks, pročež jsem udělal výjimku. Podle Rails bych měl kontrolér spíš pojmenovat GuestbookItems, což by bylo logičtější právě v množném čísle, ale mně zase logičtější přišlo pojmenovat knihu hostů Guestbook, nikoli GuestbookItems. Občas se prostě naskytnou případy, kdy je pojmenování dilema.

Tabulku jsem nicméně nechal být v čísle množném, protože scaffold generátor ji právě tak očekává. V případě blogu se kontrolér klidně může jmenovat Blogs, poněvadž bude spravovat blogy více bloggerů.

používání angličtiny

Doporučuji také používat anglické názvy úplně všude. Důvody mám dva, jednak moc pěkně znějí hybridní proměnné typu „pocet_itemu_v_blog­s“, za druhé pluralizaci Rails provádějí automaticky, a znají přitom pouze angličtinu.

Chování je změnitelné a zmnožování jde buď úplně vypnout, nebo si vytvořit vlastní pravidla, třeba pro češtinu. Pevná pravidla patří ale mezi základní výhody – podmiňují přenositelnost.

pojmenování souborů a tříd

Soubory pojmenováváme malými písmeny, jednotlivá slova oddělujeme podtržítky, např. velmi_nicnede­lajici_contro­ller.rb. Názvům souborů odpovídají názvy tříd, které jsou psány s velkými počátečními písmeny.

Příklad: Jestliže v kontroléru použijeme model GuestbookItem, Rails se automaticky podívají do adresáře app/models, zda v něm existuje soubor guestbook_item.rb. Také jsem se obával, že Rails přednačítají všechny skripty, kterých může být hodně, na začátku, ale naštěstí tomu tak není.

1. kontrolér či helper může být jak v jednotném, tak v množném čísle
2. model pouze v jednotném

bitcoin_skoleni

pojmenování databázových tabulek

create table bloggers (
   id int unsigned auto_increment,
   name VARCHAR(255),
   primary key (id)
)

create table blogs (
   id int auto_increment,
   blogger_id int unsigned,
   primary key (id),
   constraint fk_blogger_id foreign key (blogger_id) references bloggers(id)
) 

1. Všechny tabulky jsou pojmenovány v plurálu.
2. Každá tabulka by měla mít numerický primární klíč pojmenovaný id. Jiné klíče je možné specifikovat v modelu.
3. Referenční sloupec sestává z názvu tabulky, do níž referuje, v jednotném čísle, podtržítka a id (blogger_id)

v kostce

v kostce
model
Tabulka guestbook_items
Třída GuestbookItem
Soubor app/models/gu­estbook_item.rb
kontrolér
URL http://…/gues­tbook_items/list
Soubor app/controller­s/guestbook_i­tems_controller­.rb
Třída GuestbookItem­sController
Metoda akce list()
Defaultní rozvržení (layout) app/views/lay­outs/guestbook_i­tem.rhtml
pohled
URL http://…/gues­tbook_items/list
Soubor app/views/gues­tbook_items/lis­t.rhtml (.rxml)
Pomocné metody (helpery) app/helpers/gu­estbook_items_hel­per.rb, modul GuestbookItem­sHelper

Závěr

Příště se podíváme, jak ActiveRecord pracuje s referencemi mezi tabulkami, zkusíme náš blog či knihu hostů trochu vylepšit. Navíc by měla být hotova grafika. Toť (naštěstí) pro dnešek vše.

Seriál: Ruby on Rails