Renderováním je myšlen proces, při němž jsou vytvořena data k zobrazení v prohlížeči. Data vzniknou nějakým způsobem v kontroléru, defaultně použitím šablony, jejíž název odpovídá názvu kontroléru a akci. Máme-li tedy v kontroléru prázdnou akci index,
class BlogsController < ApplicationController
def index
end
end
bude nejspíš zpracována šablona views/blogs/index.rhtml. Je to totéž, jako by na konci byla volána metoda render() bez parametrů.
class BlogsController < ApplicationController
def index
render()
end
end
Nyní se podíváme na parametry metody render. Požadujeme-li renderovat šablonu, která náleží jiné akci, specifikujeme ji parametrem :action. V následujícím příkladě bude akce show simulovat akci index. Kdybychom nezavolali render sami, defaultně by se renderovala šablona views/blog/show.rhtml.
class BlogsController < ApplicationController
def index
@list = [1,2,3]
end
def show
index
render( :action => :index )
end
end
Jak ale renderovat libovolné šablony? Slouží k tomu parametr :template.
class BlogsController < ApplicationController
def show
render( :template => 'guestbook/show' )
end
end
Kromě toho lze specifikovat i jiný layout než je ten, jenž byl nastaven globálně v kontroléru, proč ne třeba zelený.
class BlogsController < ApplicationController
layout 'cerveny'
def index
if rand( 2 ) == 0
render( :layout => 'zeleny' )
end
end
end
Ovlivňováním toho, jaké šablony se budou renderovat, výčet užití metody render() nekončí. Co když nechceme použít šablonu vůbec? Samozřejmě je možné poslat prohlížeči data přímo jako řetězec. Nelze tak ovšem, naštěstí, napodobovat echo v PHP. Vícenásobné volání render totiž není možné. Kontrolér by se měl z principu starat pouze o funkčnost.
Zkusme si nadefinovat metodu s názvem „method_missing“. Ruby zavolá tuto metodu v případě, kdy bylo přistupováno k jiné, neexistující metodě téhož objektu. Jelikož akce je metodou objektu kontroléru, method_missing bude zavolána tehdy, budeme-li volat neexistující akci. V method_missing zobrazíme ladicí informace, právě pomocí render( :text ).
class BlogsController < ApplicationController
def method_missing( name, *args )
render( :text => "<html><body><h1>Neexistujici akce: #{name}</h1>Parametry: #{ params.to_yaml }</body></html>" )
end
end
A nakonec ještě nastavíme status hlavičku HTTP na jinou hodnotu než je defaultní „200 OK“.
class BlogsController < ApplicationController
def index
render( :status => '666 Very very bad', :text=>'' )
end
end
HTTP Hlavičky
Posílané HTTP hlavičky můžeme ovlivnit i jinak. S hlavičkami se pracuje jako s hashem (myšleno asociovaným polem). V příkladu budeme posílat prohlížeči náhodný obrázek (uvádím pouze pro demonstraci, používat render tímto způsobem není vhodné, viz dále).
class BlogsController < ApplicationController
def show_random_picture
num = rand( 20 )
data = File.open( RAILS_ROOT + "/public/images/random/pic#{num}.jpg" ).read
render( :text => data )
headers['Content-Type'] = 'image/jpeg'
headers['Content-Disposition'] = 'inline'
end
end
Posílání dat
Ne ke všemu se hodí render. Obecná (binární) data je lepší posílat pomocí send_data. Následující ukázka řeší zobrazení náhodného obrázku bez render(). Parametr :disposition způsobí, že se obrázek v případě akce show_random_picture zobrazí, kdežto download_random_picture vyvolá dialog k jeho stažení do souboru random.jpg.
class BlogsController < ApplicationController
def show_random_picture
num = rand( 20 )
data = File.open( RAILS_ROOT + "/public/images/random/pic#{num}.jpg" ).read
send_data( data, :type => 'image/jpeg', :disposition => 'inline' )
end
def download_random_picture
num = rand( 20 )
data = File.open( RAILS_ROOT + "/public/images/random/pic#{num}.jpg" ).read
send_data( data, :type => 'image/jpeg', :disposition => 'attachment', :filename => 'random.jpg' )
end
end
Je-li však soubor velký, není vhodné ho celý načítat do paměti. Metoda send_file je proto vhodnější, parametrem :streaming docílíme, že se soubor nenačte do paměti celý, ale bude se posílat po částech o maximálních velikostech :buffer_size. Parametr :disposition je defaultně attachment (stažení do souboru), takže ho neuvádím.
class BlogsController < ApplicationController
def download_big_file
send_file( RAILS_ROOT + "/public/big_file.dat", :filename=>'big_file.dat', :type=>'application/octet-stream', :streaming=>true, :buffer_size=>4096 )
end
end
Cookies
Obdobně jako s HTTP hlavičkami se pracuje i s cookies. Zde v jedné akci cookie nastavíme a v druhé ji přečteme.
class BlogsController < ApplicationController
def set_cookie
cookies[:mycookie] = Time.now.to_s
render( :text => "cookie byla nastavena." )
end
def show_cookie
render( :text => "cookie ma hodnotu: " + cookies[:mycookie] )
end
end
Chceme-li nastavit cookies přesněji (specifikovat expirační dobu či cestu), předáme parametry jako hash (asociované pole) namísto řetězce. Cookie s expirační dobou 0 by měla být aktivní do doby, než uživatel zavře okno prohlížeče.
class BlogsController < ApplicationController
def set_cookie
cookies[:mycookie] = {
:value=> Time.now.to_s
:path=>'/',
:expire=>0
}
render( :text => "cookie byla nastavena." )
end
end
Session
Cookies nejsou určeny k ukládání objemných dat, jednak je velikost jedné cookie omezena, jednak by bylo neefektivní, kdyby velká data prohlížeč posílal s každým požadavkem. Objemnější data se dobře udržují v session. Cookies pak obsahují pouze identifikátor konkrétní session, defaultně se session cookie nazývá _session_id.
V session můžeme udržovat nejen řetězce, nýbrž celé objekty. Před uložením jsou objekty serializovány do řetězců a před čtením opět deserializovány. Samozřejmě nelze serializovat každý objekt (například objekty tříd IO či Thread), ale jen ty objekty, které umí serializovat funkce Marshal::dump().
Do session můžeme uložit celý model. Vyhneme se tak zbytečným databázovým dotazům, pokud se databáze nemění. V takovém případě je ale nutné serializovaný model specifikovat v každém kontroléru, v němž bude používán, poněvadž třída modelu musí být načtena ještě před deserializací modelu.
class BlogsController < ApplicationController
model :blog
...
end
Také nedoporučuji do session ukládat příliš objemná data, protože by byla načítána při každém použití kontroléru, ačkoli by v něm nemusela být použita. To samé platí o ukládání důležitých dat do session. Smazáním cookie se ztratí přístup k takové session, tudíž ztratíme i data. Ukládáme-li do session celý model, deserializace nemusí proběhnout, jestliže třídu modelu mezitím výrazně změníme. Nakonec, ne každý uživatel má cookies zapnuty, přesto by stránky měly fungovat alespoň v základní podobě.
Objekty v session jsou defaultně serializovány a ukládány do souborů, existuje přesto více možností, kam s nimi. Lze je uchovávat v databázi, v souborech bez serializace, v paměti, či pomocí DRb (vzdáleného server-klient uložiště). Obvykle ale defaultní způsob (PStore) stačí.
Na rozdíl od session v PHP Rails neprovádějí odmazávání starých souborů ani záznamů v databázi. Je třeba je mazat cronem.
Způsob ukládání session se nastavuje v souboru config/environment.rb; v uváděném příkladě se používá PStore, soubory budou mít prefix sess_ a budou se nacházet v adresáři /tmp/sessions. Ostatní parametry ovlivňují nastavení session cookie.
ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS[:database_manager] = CGI::Session::PStore
ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS[:session_path] = '/'
ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS[:session_domain] = 'rolldance.cz'
ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS[:session_key] = 'RAILSESSID'
ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS[:session_expires] = Time.now.to_i + 3600
ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS[:tmpdir] = '/tmp/sessions'
ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS[:prefix] = 'sess_'
Chcete-li přesto používat databázi, v souboru config/environment.rb stačí odkomentovat jeden řádek a pomocí příkazu rake vytvořit databázovou tabulku.
# Use the database for sessions instead of the file system
# (create the session table with 'rake create_sessions_table')
# config.action_controller.session_store = :active_record_store
Více informací najdete v Rails API.
Flash
Flash nemá nic společného s programem na tvorbu animací. Jedná se o hash (asociované pole), který je používán ke komunikaci mezi dvěma stránkami (akcemi). Hash sám je uchováván v session, chová se jako session, avšak na rozdíl od session jeho obsah expiruje na následující stránce (v následující akci).
Bude-li zavolána akce save, nastaví se v ní hláška „msg“. Akce show hlášku zobrazí, ale jen při prvním volání. Po reloadu se hláška již neobjeví. Můžete si všimnout, že flash neexpiruje několikanásobným čtením v téže akci, ale jen v akci následující.
class BlogsController < ApplicationController
def save
flash[:msg] = 'Nejde to ulozit!'
end
def show
msg = flash[:msg]
msg = flash[:msg] # neexpiruje nekolikanasobnym ctenim
render( :text => msg )
end
def show2
msg = flash[:msg]
render( :text => msg )
end
end
Pomocí flash.keep lze expiraci zamezit, čímž se hodnota flashe „podrží“ do další akce. V předchozím případě by v akci show2 „msg“ již neexistovala, v následujícím existovat bude.
class BlogsController < ApplicationController
def save
flash[:msg] = 'Nejde to ulozit!'
end
def show
msg = flash[:msg]
msg = flash[:msg]
flash.keep( :msg )
render( :text => msg )
end
def show2
msg = flash[:msg]
render( :text => msg )
end
end
Flash lze použít i ke komunikaci mezi kontrolérem a šablonou. V tom případě není nutné, aby se obsah distribuoval do příští akce. Expirovat bude po skončení současné akce, a to díky flash.now.
class BlogsController < ApplicationController
def save
flash.now[:zobrazit_menu] = true
end
end
Flash slouží nejen k posílání dočasných hlášek, ale obecně jakýchkoli dat, která potřebujeme udržovat mezi dvěma akcemi. Flash můžeme v kombinaci s flash.keep používat místo session. Nemusíme pak přemýšlet, kdy a kde nepotřebná data ze session odmazávat; naopak budeme řešit, kde je udržovat, což je myslím jednodušší, neboť to bývá obvykle ta samá stránka, na níž jsou data naposledy používána.
Závěr
Příště seriál dokončíme popisem migrací, filtrů, kešování a použití některých pluginů.