Sinatra.rb aneb nejsnazší cesta k Ruby

Mohlo by se zdát, že v programovacím jazyce Ruby existuje jediný webový framework: obdivované i proklínané Ruby on Rails. Možností je však více, a na jednu z nich se nyní podíváme blíže: ukážeme si, jak na pár řádcích napsat webovou aplikaci, pracující s JSON daty, v mikro-frameworku Sinatra.
Při nezasvěceném pohledu zvenčí by se mohlo zdát, že v programovacím jazyce Ruby existuje jediný webový framework: Ruby On Rails. Zasvěcenější vědí, že existuje ještě framework Merb, který z Rails filosoficky a koncepčně vyšel. (Vývojáři obou nedávno oznámili kontroverzní spojení obou frameworků v dalších verzích.) Pro Ruby však existuje množství dalších frameworků, jako např. Mack nebo Webby, které vycházejí z odlišných principů.
Jedním z nich je i mikro-framework Sinatra Blake Mizeranyho , který je zajímavý hned z několika důvodů.
Jednak je skutečně maličký: v aktuální verzi se celý vejde do jednoho jediného souboru (45KB, 1500 řádků kódu), který si můžete celý prostudovat za půl hodiny.
Ruku v ruce s tím jde fakt, že Sinatra nepředpokládá o vaší aplikaci téměř nic: kromě toho, že bude napsaná v Ruby a bude mít URL. Na světě jste jen vy, Ruby a webový prohlížeč.
Díky tomu se velmi hodí na experimenty a zkoušení jazyka Ruby. To je asi nejdůležitější aspekt Sinatry (pozor! neskloňuje se podle vzoru „ondatra“, ale jako „Frank Sinatra“). Zejména Rails jsou pro zkoušení Ruby velmi, velmi nevhodnou platformou, neboť obsahují obrovské množství „magie“ a jsou zaměřené na rychlý návrh a implementaci webových aplikací založených na databázi. (V češtině navíc vyšla jen jedna kniha s takovým divným pánem na obalu.)
„Hello World“
Nejenom Sinatra je maličký, i aplikace v Sinatrovi je maličká. Takhle maličká:
# hello_world.rb
require 'rubygems'
require 'sinatra'
get "/" do
"Hello, it's #{Time.now} at the server!"
end
Spustíme ji jednoduše:
$ ruby hello_world.rb
== Sinatra/0.3.2 has taken the stage on port 4567 for development with backup by Rack::Handler::Mongrel
Principy
Jak vidíme, Sinatra obsahuje opravdu minimální množství infrastruktury: HTTP požadavek GET na kořenové URL vypíše na http://localhost:4567
aktuální čas. Kolem Sinatry je opravdu všechno malé: veškerá důležitá dokumentace se vejde do README souboru.
Však také Sinatra neobsahuje žádnou zabudovanou podporu pro objektově-relační mapování (ORM), pro usnadnění práce s JavaScriptem nebo AJAXem, helpery pro snadnou práci s formuláři ani generátory kódu či vše ostatní, co je na práci v Rails tak poutavé. To vše si musíte dle své vůle a potřeb přilinkovat (nebo napsat?) sami: můžete samozřejmě prostým require "activerecord"
využít obrovskou sílu ORM vrstvy z Rails. Nebo pro ORM využít úplně jinou knihovnu. Vzápětí podobným způsobem využijeme jeden z Ruby gemů pro snadnou práci s JSON.
Sinatra je filosoficky radikální v tom, že zcela vynechává ze hry koncept routování a controllerů, který nalezneme jinak v téměř každém webovém frameworku. Sinatra jednoduše mapuje určité URL (resp. HTTP požadavek) rovnou na kód aplikace. Není tedy typickým (nebo dalším) model-view-controller (MVC) frameworkem. Dle autorů frameworku není URL nic, čeho bychom se měli „bát“.
Nepotřebuje ani views („šablony“). Zobrazí prostě to, co vrátíte v bloku do...end
. Jako webový framework pochopitelně obsahuje rozsáhlou podporu nejen pro views (dynamické šablony), ale i pro helpery, tedy pomocné metody zjednodušující (dle nejlepších zásad MVC) kód ve view.
Sinatra je rovněž RESTful framework, protože kromě GET podporuje také metody POST, DELETE a PUT. Umožňuje též pracovat s podmínečným zpracováním HTTP požadavku a zaslat 304 Not Modified
, pokud má klient nacachovánu aktuální verzi.
A v neposlední řadě podporuje automatizované testování aplikace na úrovni HTTP požadavků a views pomocí jednotkových testů (nebo ostatních testovacích frameworků).
To není špatné na jeden soubor s patnácti sty řádky! Sinatra samotný je navíc jen tenkou slupkou kolem Racku, rozhraní pro webové servery, vykonávajícího Ruby kód (např. Mongrel, WEBRick, Thin, Ebb a Phusion Passenger). Jedním z důsledků je rychlost. Pomocí nástroje Apache benchmark si můžeme změřit výkon (pro zvídavé a kverulanty jsou v balíčku zdrojových kódů k článku připraveny příklady Hello World aplikací v dalších jazycích):
$ ab -n 100 -c 10 http://127.0.0.1:4567/
Requests per second: 704.34 [#/sec] (mean)
Time per request: 14.198 [ms] (mean)
Naše Hello World aplikace je samozřejmě elegantní, ale čas zjišťujeme zpravidla pohledem na mobil (staromódní a bohatí pohledem na hodinky). Měli bychom v Sinatrovi zkusit napsat něco zajímavějšího — např. klienta webové služby poskytující JSON data, typicky Twitteru.
Instalace a jednoduchá aplikace
Nejprve si musíme Sinatru nainstalovat. Pokud máme správně nainstalovaný jazyk Ruby a balíčkovací systém Rubygems, měli bychom si vystačit s jednoduchým:
$ sudo gem install sinatra
Spusťte Hello World aplikaci, jestli vše běží, jak má. Neběží-li, pište do komentářů k článku!
Nainstalujeme si pomocí příkazu $ sudo gem install json
balíček pro snadnou práci s JSON, a můžeme se pustit do klienta, zobrazujícího zprávy z oblíbeného zdroje.
Všechny následující kroky kódu si můžete stáhnout nebo zkopírovat z Git repozitáře.
První krok je jednoduchý:
require 'rubygems'
require 'sinatra'
require 'json'
require 'open-uri'
get '/' do
header 'Content-Type' => 'text/plain; charset=UTF-8'
JSON.parse( open("http://search.twitter.com/search.json?q=from%3Azdrojak").read )['results'].
collect { |r| '* ' + r['text'] }.
join("n")
end
Aplikaci spustíme pomocí $ ruby json_client.rb
a na výchozí adrese bychom měli vidět něco jako:
To nevypadá špatně na pár řádků kódu! Knihovna json
na řádku 8 zajišťuje snadné zpracování JSON dat do Ruby Hashe (asociativního pole). Takto zpracovaná data na řádku 9 projdeme pomocí collect
a vytáhneme pouze položky s klíčem text
. Na řádku 10 oddělíme položky pole koncem řádku a vrátíme jako prostý text.
Právě takováto extrémně jednoduchá webová aplikace či služba je pravou doménou Sinatry. Také jsou jedním z jeho nejznámějších produkčních nasazení webové služby pro post-receive hooky na serveru Github, hostujícím Git repositáře.
Výstup v prostém textu však zajisté není to, co od webové aplikace očekáváme. V dalším kroku do aplikace přidáme HTML kód ve view. Sinatra obsahuje zajímavou funkci právě pro podobné extrémně jednoduché aplikace: kromě views uložených v separátních souborech v adresáři views
můžete jejich kód umístit (odděleně) též do samotné aplikace:
%w{rubygems sinatra json open-uri}.each { |lib| require lib }
get '/:tweeter' do
@tweeter = params[:tweeter] || 'zdrojak'
@results = JSON.parse( open("http://search.twitter.com/search.json?q=from%3A#{@tweeter}").read )['results'].
collect { |r| { :created_at => Time.parse(r['created_at']).strftime('%d/%m %H:%M'), :text => r['text'] } }
erb :index
end
use_in_file_templates!
__END__
@@ layout
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>JSON Client</title>
</head>
<body>
<h1>Novinky od <em><%= @tweeter %></em></h1>
<%= yield %>
</body>
</html>
@@ index
<ul>
<% @results.each do |result| %>
<li><em><%= result[:created_at] %>:</em> <%= result[:text] %></li>
<% end %>
</ul>
V tomto kroku jsme přidali dynamické vypisování vybraného Twitter feedu podle parametru tweeter
, předaného přímo v URL (řádek 3). Na řádku 6 jsme převedli JSON data do jiné struktury a na řádku 7 renderujeme view index
pomocí šablonovacího systému ERB. Díky deklaraci use_in_file_templates!
na řádku 10 Sinatra hledá kód pro views — obecnou „slupku“ layout
a vnitřní šablonu index
— v konstantě DATA
(cokoliv za __END__
). Samozřejmě pro aplikace se složitějšími views je vhodné je umisťovat do jejich vlastního adresáře.
Rozšíření aplikace
Abychom si předvedli, čeho jsou Ruby se Sinatrou schopni, můžeme naši jednoduchou službu v posledním kroku ještě rozšířit o jednoduché cachování. V tomto případě se již vyplatí převést logiku aplikace do objektového modelu. Kód zde vypisujeme zkrácený — v úplnosti si jej prohlédněte v souborujson_client.rb
v repozitáři.
class TweetReader
# ...
def initialize(id)
@id = id
end
# ...
def load
if cached? and not stale?
load_cached
else
load_remote and cache!
end
end
private
# ...
def load_cached
puts "* Loading cached data"
@results = YAML.load_file( cached_file )
end
def load_remote
puts "* Loading data from remote service"
@results = JSON.parse( open("http://search.twitter.com/search.json?q=from%3A#e_13").read )['results'].collect do |r|
{ :created_at => Time.parse(r['created_at']).strftime('%d/%m %H:%M'), :text => r['text'] }
end
end
def cache!
File.open(cached_file, 'w') { |file| YAML.dump(@results, file) }
end
def stale?
File.mtime(cached_file) < Time.now-20 # Expirace po 20 sekundach
end
# ...
end
get '/' do
redirect( "/#{DEFAULT_TWEETER}" )
end
get '/:tweeter' do
@tweeter = TweetReader.new( params[:tweeter] || DEFAULT_TWEETER )
if time = @tweeter.last_modified # 304 Not Modified, pokud se zdroj dat nezmenil
last_modified( time )
end
@tweeter.load
erb :index
end
...
Možná z uvedeného kódu vidíte, proč některé programátory jazyk Ruby tak přitahuje: obsahuje velké množství podpůrných knihoven (jako v našem případě pro serializaci do YAML a pro práci s filesystémem) a umožňuje bez velké námahy psát čitelný, objektový kód: if cached? and not stale? load_cached()
a podobně. Samotná Sinatra aplikace pak díky tomu může mít jedenáct řádků.
Díky nedávné podpoře rozhraní Rack v Rails, můžete dokonce aplikace v Sinatrovi (stejně jiné Rack-kompatibilní aplikace) spouštět přímo jako součást větší Rails aplikace.
Sinatru můžeme snadno použít jako vizuální rozhraní pro zkoušky a cvičení v technikách objektově orientovaného programování, návrhových vzorů nebo tvorbě vlastních domain specific languages. Právě pro takové „průzkumné“ programování je Sinatra jako stvořený. Profese programátora je dnes náročná díky tomu, že celá IT sféra se pohybuje velmi, velmi rychlým tempem — a v každodenní rutině je leckdy obtížné nebo nemožné najít čas a prostor pro nezávazné zkoušení, „skicování“ a programování „zbytečností“. A bez cvičení se žádné řemeslo naučit nedá.
Hosting a další informace
Chcete-li si vyzkoušet aplikaci v Sinatrovi na serveru a nemáte možnost nebo nechcete instalovat Ruby, Gemy a aplikační server, můžete si ji jednoduše nainstalovat na Ruby hosting zadarmo od společnosti Kraxnet. Sinatra je totiž stejně jako Ruby on Rails kompatibilní s Apache modulem mod_rails (Phusion Passenger).
Zdrojové kódy použité v článku si můžete stáhnout: Ukázka frameworku Sinatra (ZIP 2508 bytů).
Další informace hledejte zejména v těchto zdrojích:
- README na stránkách Sinatry
- Informace o deploymentu na serveru hledejte v open-source knize o Sinatrovi
- Server RubyInside před časem publikoval seznam reprezentativních odkazů k Sinatrovi
- Rozsáhlý seznam mnoha aplikací, knihoven a rozšíření naleznete v pastie na serveru Github
- Zhlédněte video z přednášky na konferenci RubyConf 2008 (doprovodné slidy najdete na serveru Slideshare)
Pokud vám cokoliv z uvedených příkladů nebude fungovat správně, napište prosím do komentářů. A dejte vědět, jak vaše experimenty se Sinatrou dopadly!
Halo, jen tak pro zajimavost, mohl by nekdo rict, co je ruby? Kdyz nekdo zna perl, php a videl python, porozumi k cemu je vytvoren ruby? Dik za strucny uvod/trailer do ruby….
Ale prosím. Nemožné děláme na počkání, zázraky pak do druhého dne. Libo snad některý z následujících seriálů?
Dovolil bych si spíše doporučit:
–> http://www.ruby-lang.org/en/documentation/ruby-from-other-languages/
Četl jsem jenom sekci věnovanou podobnostem a rozdílům oproti Pythonu a říká dost málo. Spíš propaganda, než užitečná informace.
Ta je zrovna docela krátká — ale přijde mi docela pragmatická a objektivní, na základě čeho přesně se to zdá jako "propaganda"?
Ruby má mnohem lepší podporu pro metaprogramování a např. tvorbu DSL
len skoda za sa sinatra nejak nevyvija, commity na githube su riedke a stare, ale inak svoj ucel plni aj v stave v akom je, cize ubereasy, ultrapocket
Nene, *naprostý* opak je pravdou :) Vývoj Sinatry je nyní rozprostřen a neprobíhá jen v hlavní větvi Blake Mizeranyho (http://github.com/bmizerany/sinatra/commits/). I když, i tam je commit každých pár dní :)
Díky síle Gitu a Githubu probíhá zásadní refaktoring ve forku Ryana Tomayka: http://github.com/rtomayko/sinatra/commits/master . Uvažované změny se týkají zejména kompatibility s ostatním Rack middleware, zpřehlednění kódu, deprecation redundantních částí API, apod. Pro kontext prosím čtěte tyto dvě oznámení: http://groups.google.com/group/sinatrarb/msg/0f65eedf125f0dde?hl=en a http://groups.google.com/group/sinatrarb/msg/7e4eca1bf28bb355?hl=en
ode dneska uz je sinatra k dispozici na:
http://github.com/sinatra/sinatra/tree/master
:)
Je to tak: http://groups.google.com/group/sinatrarb/msg/a405d1b851f57960?hl=en .)
Zde najdete changelog pro novou verzi Sinatry (kompatibilní s Rack 0.9):
–> http://github.com/sinatra/sinatra/raw/master/CHANGES
Celkem rád bych si to vyzkoušel, ale nejde mi to naistalovat.
gem install sinatra
jenom řekne:Přitom rack mám ve verzi 0.9.1. Pokud Sinatru nainstaluju s přepínačem
--force
, tak to stejně nejde spustit:Tak to jsem rád, že s tím nezápasím sám. Mě se teda podařilo sinatru nainstalovat (na debian stable), ale už se mi nepodařilo spustit HalloWorld.
postupoval jsem následovně:
# apt-get install ruby libgems-ruby1.8 irb rubygems git libzlib-ruby
# gem install sinatra
~/work$ cat myapp.rb
# myapp.rb
require 'rubygems'
require 'sinatra'
get '/' do
'Hello world!'
end
~/work$ ruby myapp.rb
/var/lib/gems/1.8/gems/sinatra-0.3.3/lib/sinatra.rb:110:in `server': /usr/lib/ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require': no such file to load — mongrel (LoadError)
from /usr/lib/ruby/1.8/rubygems/custom_require.rb:27:in `require'
from /var/lib/gems/1.8/gems/rack-0.4.0/lib/rack/handler/mongrel.rb:1
from (eval):1:in `server'
from /var/lib/gems/1.8/gems/sinatra-0.3.3/lib/sinatra.rb:115:in `eval'
from /var/lib/gems/1.8/gems/sinatra-0.3.3/lib/sinatra.rb:110:in `server'
from /var/lib/gems/1.8/gems/sinatra-0.3.3/lib/sinatra.rb:115:in `run'
from /var/lib/gems/1.8/gems/sinatra-0.3.3/lib/sinatra.rb:1481
from myapp.rb:4
Něco je špatně, ale co… zatr.
Podle té chyby bych zkusil doinstalovat ještě 'mongrel'.
Správně, chcípe to na nedostupném Mongrelu (webserver v Ruby), což je divné, protože by se měl automaticky při nedostupném Mongrelu přepnout na Webrick (a ten je bundlovaný s Ruby).
Zkuste `$ sudo gem install mongrel` a pak uvidíme dál .)
gem install mongrel vypada jako spravna cesta :)
nicmene nejprve mam na vyber z 72 verzi mongrelu.. tak jsem zvolil prvni moznost
mongrel 1.1.5 (ruby)
pote jsem vyzvan k instalaci zavisleho fastthread s cimz souhlasim a volim verzi 1.0.1 (ruby)
a instalace fastthread konci chybou:
Building native extensions. This could take a while…
extconf.rb:1:in `require': no such file to load — mkmf (LoadError)
from extconf.rb:1
ERROR: While executing gem … (RuntimeError)
ERROR: Failed to build gem native extension.
Gem files will remain installed in /var/lib/gems/1.8/gems/fastthread-1.0.1 for inspection.
Results logged to /var/lib/gems/1.8/gems/fastthread-1.0.1/ext/fastthread/gem_make.out
porad neco chybi.. mkmf reklbych
Jo, všechny gem-y, které potřebují něco kompilovat, tak chtějí po systému compiler, knihovny a jiné srandy. Určitě je tedy třeba doinstalovat ruby1.8-dev balíček. Ten by měl potáhnout další development balíčky pokud již nejsou nainstalovány.
Existuje jeste jedna moznost, pokud se nepodari nainstalovat ten Mongrel.
Nainstalovat si server Thin, jak radil @pm:
A potom v HelloWorld aplikaci Thin prilinkovat:
Kdyz pak „normalne“ spustite aplikaci:
mela by si vybrat Thin misto Mongrelu jako Rack handler:
Hmmm, tady bych osobně zkusil klasický "dementní" postup: odinstalovat Rack a pak nainstalovat Sinatru (a s ním i Rack).
Kdyby nešlo, napište verze Ruby, Rubygems, Racku.
(Je taky možné, že ty závislosti (dependencies) Sinatry jsou špatně uvedené nebo implementované. Zkuste nejdřív odinstalace/instalace.)
v gems je posledni verze sinatry, ktera nepodporuje rack 0.9.
postup, ktery zarucene funguje (odzkouseno na OpenSUSE 11, ale urcite to pojede i na debianu, pokud ne, tak pisnete chyby)
(Successfully installed rack-0.9.1)
(Successfully installed thin-1.0.0)
cat test-sin.rb
pak uz jen
@pm: Díky za návod!
Jedno řešení je tohle: použít edge. Druhé, možná zatím lepší řešení je nainstalovat si Rack verze 0.4 — Sinatra je skutečně aktuálně zamčený na tuhle verzi. Instalace:
Zkuste, dejte vědět.
Mockrát děkuji, s touhle vynucenou verzí vše funguje jak má.
"Při nezasvěceném pohledu zvenčí by se mohlo zdát, že v programovacím jazyce Ruby existuje jediný webový framework"
Ne, pri nezasvecen pohledu by se mohlo zdat, ze v Ruby se bez posahanych frameworku neda vubec nic delat.
opravdu zacinam nenavidet ty naprosto nepruhledne linky. Uz jsme jen krok od "kliknete +zde+, +zde+ a +zde+.
To je opravdu takovy problem napsat ze se sinatra da pouzivat s datamapperem a sequelemh?
Jinak pekny clanek.
Nevidím to jako problém. Názvu obou projektů jsou v tom případě jen doplňující informace k textu článku. Základní a potřebné informace jsou přímo v textu uvedeny.
Copak Vy…
V gemech je nyní dostupná nová verze Sinatry, která je kompatibilní s Rack 0.9.1, viz zpráva: http://groups.google.com/group/sinatrarb/msg/1fbc3b0311f256c7?hl=en
Pěkný článek, díky za něj. Měl bych jen pár připomínek:
– instaloval bych radši "json-pure" než "json", "json" je sice rychlejší, ale taky provázaná s Céčkem, takže nebude fungovat v (neupravených) Windows
– File.mtime(cached_file) – ve Windows to vyhodí exception, pokud soubor neexistuje
– mohl ses zmínit, že "last_modified()" je built-in helper pro Sinatru, trochu mě to zmátlo