Přejít k navigační liště

Zdroják » Různé » Sinatra.rb aneb nejsnazší cesta k Ruby

Sinatra.rb aneb nejsnazší cesta k Ruby

Články Různé

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:

Screenshot Sinatra aplikace - JSON klient

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í RackRails, 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:

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!

Jaký framework v Ruby používáte?

Komentáře

Subscribe
Upozornit na
guest
28 Komentářů
Nejstarší
Nejnovější Most Voted
Inline Feedbacks
View all comments
mirozbiro

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….

Martin Hassman

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ů?

karmi
Inkvizitor

Č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.

karmi

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"?

darth

Ruby má mnohem lepší podporu pro metaprogramování a např. tvorbu DSL

skrat

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

karmi

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

pm

ode dneska uz je sinatra k dispozici na:

http://github.com/sinatra/sinatra/tree/master

:)

karmi
karmi

Zde najdete changelog pro novou verzi Sinatry (kompatibilní s Rack 0.9):

–> http://github.com/sinatra/sinatra/raw/master/CHANGES

zoufalec

Celkem rád bych si to vyzkoušel, ale nejde mi to naistalovat. gem install sinatra jenom řekne:

ERROR:  Error installing sinatra:
	sinatra requires rack (~> 0.4.0, runtime)

Přitom rack mám ve verzi 0.9.1. Pokud Sinatru nainstaluju s přepínačem --force, tak to stejně nejde spustit:

/usr/lib/ruby/1.8/rubygems.rb:578:in `report_activate_error': RubyGem version error: rack(0.9.1 not ~> 0.4.0) (Gem::LoadError)
tmp

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.

zoufalec

Podle té chyby bych zkusil doinstalovat ještě 'mongrel'.

karmi

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 .)

tmp

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

Kamil

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.

karmi

Existuje jeste jedna moznost, pokud se nepodari nainstalovat ten Mongrel.

Nainstalovat si server Thin, jak radil @pm:

$ sudo gem install thin

A potom v HelloWorld aplikaci Thin prilinkovat:

require 'rubygems'
require 'sinatra'
require 'thin'
get "/" do
  "Hello, it's #{Time.now} at the server!"
end

Kdyz pak „normalne“ spustite aplikaci:

ruby hello_world.rb

mela by si vybrat Thin misto Mongrelu jako Rack handler:

== Sinatra/0.3.3 has taken the stage on port 4567 for development with backup by Rack::Handler::Thin
karmi

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.)

pm

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)

gem install rack


(Successfully installed rack-0.9.1)

gem install thin


(Successfully installed thin-1.0.0)

mkdir test-sin && cd test-sin 


git clone git://github.com/rtomayko/sinatra/


cat test-sin.rb

require 'rubygems'
$:.unshift File.dirname(__FILE__) + '/sinatra/lib'
require 'sinatra'

get '/about' do
  "I'm running on Version " + Sinatra::VERSION
end


pak uz jen

 run test-sin.rb 

karmi

@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:

$ sudo gem install rack -v 0.4

Zkuste, dejte vědět.

zoufalec

Mockrát děkuji, s touhle vynucenou verzí vše funguje jak má.

Anonymní

"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.

DarkTatka

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.

Martin Hassman

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.

Anonymní

Copak Vy…

karmi

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

lucastej

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

Enum a statická analýza kódu

Mám jednu univerzální radu pro začínající programátorty. V učení sice neexistují rychlé zkratky, ovšem tuhle radu můžete snadno začít používat a zrychlit tak tempo učení. Tou tajemnou ingrediencí je statická analýza kódu. Ukážeme si to na příkladu enum.