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_development připravenou. Musíme Rails nakonfigurovat, aby ji používaly. Editujte soubor config/database.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::Base.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://localhost:3000/guestbook
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/controllers/guestbook_controller.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/guestbook.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/controllers/guestbook_controller.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://localhost:3000/guestbook (= http://localhost:3000/guestbook/index)
http://localhost:3000/guestbook/new
http://localhost:3000/guestbook/create
http://localhost:3000/guestbook/list
http://localhost:3000/guestbook/show
http://localhost:3000/guestbook/edit
http://localhost:3000/guestbook/update
http://localhost:3000/guestbook/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/guestbook.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/guestbook/_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://localhost: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/layouts/ přejmenujte na rolldance.rhtml a druhý smažte.
app/views/layouts/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 GuestbookController, tabulka je pojmenována guestbooks. Ostatně, kontrolér by byl také GuestbooksController, 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_blogs“, 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_nicnedelajici_controller.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
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
model | ||
---|---|---|
Tabulka | guestbook_items | |
Třída | GuestbookItem | |
Soubor | app/models/guestbook_item.rb | |
kontrolér | ||
URL | http://…/guestbook_items/list | |
Soubor | app/controllers/guestbook_items_controller.rb | |
Třída | GuestbookItemsController | |
Metoda akce | list() | |
Defaultní rozvržení (layout) | app/views/layouts/guestbook_item.rhtml | |
pohled | ||
URL | http://…/guestbook_items/list | |
Soubor | app/views/guestbook_items/list.rhtml (.rxml) | |
Pomocné metody (helpery) | app/helpers/guestbook_items_helper.rb, modul GuestbookItemsHelper |
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.