Внимание: Этот документ является переводом Английской версии и может быть устаревшим
Sinatra — это предметно-ориентированный язык (DSL) для быстрого создания приложений на Ruby с приложением минимума усилий:
# myapp.rb
require 'sinatra'
get '/' do
'Hello world!'
endУстановите gem и запустите приложение с помощью:
gem install sinatra
ruby -rubygems myapp.rbРезультат будет тут: localhost:4567
В Sinatra маршрут — это пара: HTTP метод и шаблон (образец) URL. Каждый маршрут ассоциирован с блоком:
get '/' do
.. что-то показать ..
end
post '/' do
.. что-то создать ..
end
put '/' do
.. что-то обновить ..
end
delete '/' do
.. что-то удалить ..
end
options '/' do
.. что-то ответить ..
endМаршруты сверяются с запросом по очередности определения. Первый же совпавший с запросом маршрут и будет вызван.
Шаблоны маршрутов могут включать в себя параметры доступные в params xэше:
get '/hello/:name' do
# соответствует "GET /hello/foo" и "GET /hello/bar",
# где params[:name] 'foo' или 'bar'
"Hello #{params[:name]}!"
endМожно также использовать именные параметры в переменных блоков:
get '/hello/:name' do |n|
"Hello #{n}!"
endШаблоны маршрутов также могут включать splat (wildcard, *, любая строка символов) параметры доступные в params[:splat] массиве.
get '/say/*/to/*' do
# соответствует /say/hello/to/world
params[:splat] # => ["hello", "world"]
end
get '/download/*.*' do
# соответствует /download/path/to/file.xml
params[:splat] # => ["path/to/file", "xml"]
endМаршруты также могут использовать регулярные выражения в качестве шаблона URL:
get %r{/hello/([\w]+)} do
"Hello, #{params[:captures].first}!"
endИли с параметром блока:
get %r{/hello/([\w]+)} do |c|
"Hello, #{c}!"
endМаршруты могут включать различные условия совпадений, такие как user agent:
get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do
"You're using Songbird version #{params[:agent][0]}"
end
get '/foo' do
# соответствует с non-songbird браузерам
endДругими доступными условиями являются host_name и provides:
get '/', :host_name => /^admin\./ do
"Admin Area, Access denied!"
end
get '/', :provides => 'html' do
haml :index
end
get '/', :provides => ['rss', 'atom', 'xml'] do
builder :feed
endДовольно легко можно задать собственные условия:
set(:probability) { |value| condition { rand <= value } }
get '/win_a_car', :probability => 0.1 do
"You won!"
end
get '/win_a_car' do
"Sorry, you lost."
endВозвращаемое значение блока маршрута ограничивается телом ответа, которое будет передано HTTP клиенту, или следующей подпрограммой (middleware) в Rack стеке. Чаще всего это строка, как в вышеизложенных примерах. Но и другие значения также приемлемы.
Вы можете вернуть любой объект, который будет либо корректным Rack ответом, Rack телом ответа, либо кодом состояния HTTP:
-
Массив с тремя переменными:
[status (Fixnum), headers (Hash), response body (должен отвечать на #each)] -
Массив с двумя переменными:
[status (Fixnum), response body (должен отвечать на #each)] -
Объект, отвечающий на
#each, который передает только строковые типы данных в этот блок -
Fixnum, соответствующий коду состояния HTTP
Таким образом мы легко можем создать поточный пример:
class Stream
def each
100.times { |i| yield "#{i}\n" }
end
end
get('/') { Stream.new }Статические файлы отдаются из ./public директории. Вы можете указать другое место, используя :public опцию:
set :public, File.dirname(__FILE__) + '/static'Учтите, что имя директории со статическими файлами не включено в URL. Например, файл ./public/css/style.css будет доступен как http://example.com/css/style.css.
Шаблоны по умолчанию будут использованы из директории ./views. Для использования другой директории:
set :views, File.dirname(__FILE__) + '/templates'Важно помнить, что вы всегда должны указывать шаблоны с помощью символов, даже если это подкаталог (в этом случае используйте :'subdir/template'). Вы должны использовать символ, иначе методы, ответственные за рендеринг, отобразят просто переданную им строку.
Haml gem/библиотека необходима для рендеринга HAML шаблонов:
# Вам нужно будет подключить haml gem в приложении
require 'haml'
get '/' do
haml :index
endОтрисует ./views/index.haml.
Опции Haml могут быть установлены глобально через конфигурацию Sinatra, см. Опции и Конфигурация, и переопределены локально.
set :haml, :format => :html5 # :xhtml - Haml формат по умолчанию
get '/' do
haml :index, :format => :html4 # переопределен
end# Вам нужно будет подключить erb в приложении
require 'erb'
get '/' do
erb :index
endОтрисует ./views/index.erb
Erubis gem/библиотека необходима для рендеринга erubis шаблонов:
# Вам нужно будет подключить erubis в приложении
require 'erubis'
get '/' do
erubis :index
endОтрисует ./views/index.erubis
Builder gem/библиотека необходима для рендеринга builder шаблонов:
# Вам нужно будет подключить builder в приложении
require 'builder'
get '/' do
builder :index
endОтрисует ./views/index.builder.
Nokogiri gem/библиотека необходима для рендеринга nokogiri шаблонов:
# Вам нужно будет подключить nokogiri в приложении
require 'nokogiri'
get '/' do
nokogiri :index
endОтрисует ./views/index.nokogiri.
Haml gem/библиотека необходима для рендеринга Sass шаблонов:
# Вам нужно будет подключить haml или sass в приложении
require 'sass'
get '/stylesheet.css' do
sass :stylesheet
endОтрисует ./views/stylesheet.sass.
Опции Sass могут быть установлены глобально через конфигурацию Sinatra, см. Опции и Конфигурация, и переопределены локально.
set :sass, :style => :compact # :nested - стиль Sass по умолчанию
get '/stylesheet.css' do
sass :stylesheet, :style => :expanded # переопределен
endHaml gem/библиотека необходима для рендеринга Scss шаблонов:
# Вам нужно будет подключить haml или sass в приложении
require 'sass'
get '/stylesheet.css' do
scss :stylesheet
endОтрисует ./views/stylesheet.scss.
Опции Scss могут быть установлены глобально через конфигурацию Sinatra, см. Опции и Конфигурация, и переопределены локально.
set :scss, :style => :compact # :nested - стиль Scss по умолчанию
get '/stylesheet.css' do
scss :stylesheet, :style => :expanded # переопределен
endless gem/библиотека необходима для рендеринга Less шаблонов:
# Вам нужно будет подключить less в приложении
require 'less'
get '/stylesheet.css' do
less :stylesheet
endОтрисует ./views/stylesheet.less.
liquid gem/библиотека необходима для рендеринга liquid шаблонов:
# Вам нужно будет подключить liquid в приложении
require 'liquid'
get '/' do
liquid :index
endОтрисует ./views/index.liquid.
Так как в Liquid шаблонах невозможно вызывать методы из Ruby (кроме yield), то вы почти всегда будете передавать локальные переменные:
liquid :index, :locals => { :key => 'value' }rdiscount gem/библиотека необходима для рендеринга Markdown шаблонов:
# Вам нужно будет подключить rdiscount в приложении
require "rdiscount"
get '/' do
markdown :index
endОтрисует ./views/index.markdown (md и mkd также являются допустимыми файловыми расширениями).
В markdown невозможно вызывать методы или передавать локальные переменные. Следовательно, вам скорее всего придется использовать этот шаблон совместно с другим движком рендеринга:
erb :overview, :locals => { :text => markdown(:introduction) }Заметьте, что вы можете вызывать метод markdown из других шаблонов:
%h1 Hello From Haml!
%p= markdown(:greetings)RedCloth gem/библиотека необходима для рендеринга Textile шаблонов:
# Вам нужно будет подключить redcloth в приложении
require "redcloth"
get '/' do
textile :index
endОтрисует ./views/index.textile.
В textile невозможно вызывать методы или передавать локальные переменные. Следовательно, вам скорее всего придется использовать этот шаблон совместно с другим движком рендеринга:
erb :overview, :locals => { :text => textile(:introduction) }Заметьте, что вы можете вызывать метод textile из других шаблонов:
%h1 Hello From Haml!
%p= textile(:greetings)RDoc gem/библиотека необходима для рендеринга RDoc шаблонов:
# Вам нужно будет подключить rdoc в приложении
require "rdoc"
get '/' do
rdoc :index
endОтрисует ./views/index.rdoc.
В rdoc невозможно вызывать методы или передавать локальные переменные. Следовательно, вам скорее всего придется использовать этот шаблон совместно с другим движком рендеринга:
erb :overview, :locals => { :text => rdoc(:introduction) }Заметьте, что вы можете вызывать метод rdoc из других шаблонов:
%h1 Hello From Haml!
%p= rdoc(:greetings)radius gem/библиотека необходима для рендеринга Radius шаблонов:
# Вам нужно будет подключить radius в приложении
require 'radius'
get '/' do
radius :index
endОтрисует ./views/index.radius.
Так как в Radius шаблоне невозможно вызывать методы из Ruby (кроме yield), то вы почти всегда будете передавать локальные переменные:
radius :index, :locals => { :key => 'value' }markaby gem/библиотека необходима для рендеринга Markaby шаблонов:
# Вам нужно будет подключить markaby в приложении
require 'markaby'
get '/' do
markaby :index
endОтрисует ./views/index.mab.
Если у вас установлен Tilt версии 1.2 или выше, то вы также можете использовать внутристроковые markaby шаблоны:
get '/' do
markaby { h1 "Welcome!" }
endslim gem/библиотека необходима для рендеринга slim шаблонов:
# Вам нужно будет подключить slim в приложении
require 'slim'
get '/' do
slim :index
endОтрисует ./views/index.slim.
coffee-script gem/библиотека и coffee бинарный файл необходимы для рендеринга CoffeeScript шаблонов:
# Вам нужно будет подключить coffee-script в приложении
require 'coffee-script'
get '/application.js' do
coffee :application
endОтрисует ./views/application.coffee.
get '/' do
haml '%div.title Hello World'
endОтрисует внутристроковый шаблон.
Шаблоны интерпретируются в том же контексте, что и обработчики маршрутов. Переменные экзмепляра, установленные в процесе обработки маршрутов, будут доступны напрямую в шаблонах:
get '/:id' do
@foo = Foo.find(params[:id])
haml '%h1= @foo.name'
endЛибо установите их через хеш локальных переменных:
get '/:id' do
foo = Foo.find(params[:id])
haml '%h1= foo.name', :locals => { :foo => foo }
endЭто обычно используется когда шаблоны рендерятся как частные (partials) из других шаблонов.
Шаблоны также могут быть определены в конце файла-исходника:
require 'sinatra'
get '/' do
haml :index
end
__END__Заметьте: Вложенные шаблоны, определенные в файле-исходнике, который подключил sinatra, будут автоматически загружены. Вызовите enable :inline_templates напрямую, если у вас вложенные шаблоны в других файлах.
Шаблоны также могут быть определены, используя template метод:
template :layout do
"%html\n =yield\n"
end
template :index do
'%div.title Hello World!'
end
get '/' do
haml :index
endЕсли шаблон с именем “layout” существует, то он будет использован каждый раз, когда шаблоны будут отрисовываться. Вы можете отключить layout-шаблон с помощью :layout => false.
get '/' do
haml :index, :layout => !request.xhr?
endИспользуйте helpers метод для определения методов помощников для дальнейшего использования в обработчиках маршрутов и шаблонах:
helpers do
def bar(name)
"#{name}bar"
end
end
get '/:name' do
bar(params[:name])
endBefore-фильтры выполняются перед каждым запросом в том же контексте, что и маршруты. Фильтры могут изменять как запрос, так и ответ на него. Переменные экземпляра, установленные в фильтрах, доступны в маршрутах и шаблонах:
before do
@note = 'Hi!'
request.path_info = '/foo/bar/baz'
end
get '/foo/*' do
@note #=> 'Hi!'
params[:splat] #=> 'bar/baz'
endAfter-фильтры выполняются после каждого запроса в том же контексте, что и пути. Фильтры могут изменять как запрос, так и ответ на него. Переменные экземпляра, установленные в before-фильтрах и маршрутах, будут доступны в after-фильтрах:
after do
puts response.status
endФильтры могут использовать шаблоны URL и будут интерпретированы, только если путь запроса совпадет с этим шаблоном:
before '/protected/*' do
authenticate!
end
after '/create/:slug' do |slug|
session[:last_slug] = slug
endКак и маршруты, фильтры могут использовать условия:
before :agent => /Songbird/ do
# ...
end
after '/blog/*', :host_name => 'example.com' do
# ...
endЧтобы незамедлительно прервать обработку запроса внутри фильтра или маршрута, используйте:
haltМожно также указать статус при прерывании:
halt 410Тело:
halt 'this will be the body'И то, и другое:
halt 401, 'go away!'Можно указать заголовки:
halt 402, {'Content-Type' => 'text/plain'}, 'revenge'Маршрут может передать обработку запроса следующему совпадающему маршруту, используя pass:
get '/guess/:who' do
pass unless params[:who] == 'Frank'
'You got me!'
end
get '/guess/*' do
'You missed!'
endБлок маршрута сразу же прерывается, и контроль переходит к следующему совпадающему маршруту. Если соответствующий маршрут не найден, то ответом на запрос будет 404.
Объект входящего запроса доступен на уровне обработки запроса (в фильтрах, маршрутах, обработчиках ошибок) с помощью request метода:
# приложение запущено на http://example.com/example
get '/foo' do
request.body # тело запроса, посланное клиентом (см. ниже)
request.scheme # "http"
request.script_name # "/example"
request.path_info # "/foo"
request.port # 80
request.request_method # "GET"
request.query_string # ""
request.content_length # длина тела запроса
request.media_type # медиа тип тела запроса
request.host # "example.com"
request.get? # true (для других участвующих HTTP глаголов есть похожие методы)
request.form_data? # false
request["SOME_HEADER"] # значение SOME_HEADER заголовка
request.referer # источник запроса клиента либо '/'
request.user_agent # user agent (используется для :agent условия)
request.cookies # хеш куки браузера
request.xhr? # является ли запрос ajax запросом?
request.url # "http://example.com/example/foo"
request.path # "/example/foo"
request.ip # IP адрес клиента
request.secure? # false
request.env # env хеш как получено Rack
endНекоторые опции, такие как script_name или path_info могут быть переписаны:
before { request.path_info = "/" }
get "/" do
"all requests end up here"
endrequest.body является IO или StringIO объектом:
post "/api" do
request.body.rewind # в случае, если кто-то уже прочитал тело запроса
data = JSON.parse request.body.read
"Hello #{data['name']}!"
endЭтот блок исполняется один раз при старте в любом окружении, режиме (environment):
configure do
...
endБудет запущено, когда окружение (RACK_ENV переменная) установлена в :production:
configure :production do
...
endБудет запущено, когда окружение :production или :test:
configure :production, :test do
...
endОбработчики ошибок исполняются в том же контексте, что и маршруты, before-фильтры, а это означает, что всякие прелести вроде haml, erb, halt и т.д. доступны и им.
Когда возбуждено исключение Sinatra::NotFound, или кодом ответа является 404, то будет вызван not_found обработчик:
not_found do
'This is nowhere to be found.'
endОбработчик ошибок error будет вызван, когда исключение возбуждено из блока маршрута, либо из фильтра. Объект-исключение доступен как переменная sinatra.error в Rack:
error do
'Sorry there was a nasty error - ' + env['sinatra.error'].name
endЧастные ошибки:
error MyCustomError do
'So what happened was...' + request.env['sinatra.error'].message
endТогда, если это произошло:
get '/' do
raise MyCustomError, 'something bad'
endТо вы получите:
So what happened was... something badТакже вы можете установить обработчик ошибок для кода состояния HTTP:
error 403 do
'Access forbidden'
end
get '/secret' do
403
endЛибо набора кодов:
error 400..510 do
'Boom'
endSinatra устанавливает специальные not_found и error обработчики, когда запущена в режиме разработки (окружение :development).
Когда вы используете send_file или статические файлы, у вас могут быть mime типы, которые Sinatra не понимает по умолчанию. Используйте mime_type для их регистрации по расширению файла:
mime_type :foo, 'text/foo'Вы также можете использовать это в content_type помощнике:
content_type :fooSinatra использует Rack, минимальный стандартный интерфейс для веб-фреймворков на Ruby. Одной из самых интересных для разработчиков возможностей Rack является поддержка подпрограмм (“middleware”) – компонентов, “сидящих” между сервером и вашим приложением, которые отслеживают и/или манипулируют HTTP запросами/ответами для предоставления различной функциональности.
В Sinatra очень просто использовать такие Rack подпрограммы с помощью метода use:
require 'sinatra'
require 'my_custom_middleware'
use Rack::Lint
use MyCustomMiddleware
get '/hello' do
'Hello World'
endСемантика use идентична той, что определена для Rack::Builder DSL (чаще всего используется в rackup файлах). Например, use метод принимает множественные переменные, также как и блоки:
use Rack::Auth::Basic do |username, password|
username == 'admin' && password == 'secret'
endRack распространяется с различными стандартными подпрограммами для логирования, отладки, маршрутизации URL, аутентификации, обработки сессий. Sinatra использует многие из этих компонентов автоматически, основываясь на конфигурации, чтобы вам не приходилось регистрировать/использовать (use) их вручную.
Тесты для Sinatra приложений могут быть написаны с помощью библиотек, фреймворков, поддерживающих тестирование Rack. Rack::Test рекомендован:
require 'my_sinatra_app'
require 'test/unit'
require 'rack/test'
class MyAppTest < Test::Unit::TestCase
include Rack::Test::Methods
def app
Sinatra::Application
end
def test_my_default
get '/'
assert_equal 'Hello World!', last_response.body
end
def test_with_params
get '/meet', :name => 'Frank'
assert_equal 'Hello Frank!', last_response.body
end
def test_with_rack_env
get '/', {}, 'HTTP_USER_AGENT' => 'Songbird'
assert_equal "You're using Songbird!", last_response.body
end
endЗаметьте: Встроенные модули Sinatra::Test и Sinatra::TestHarness являются устаревшими, начиная с 0.9.2 релиза.
Описание своего приложения самым простейшим способом (с помощью DSL верхнего уровня, как в примерах выше) работает отлично для крохотных приложений, но имеет множество недостатков, когда надо создать компоненты, такие как Rack middleware, Rails metal, простые библиотеки с серверными компонентами, Sinatra расширения. DSL верхнего уровня загрязняет пространство имен Object и подразумевает стиль конфигурации микро-приложения (например, единый файл приложения, ./public и ./views директории, создание логов, страницу деталей об исключениях и т.д.). И тут на помощь приходит Sinatra::Base:
require 'sinatra/base'
class MyApp < Sinatra::Base
set :sessions, true
set :foo, 'bar'
get '/' do
'Hello world!'
end
endMyApp класс является независимым Rack компонентом, который может исполнять роли Rack подпрограммы, Rack приложения, Rails metal. Вы можете use (использовать) или run (запустить) этот класс из rackup файла config.ru; или контролировать серверную часть из библиотеки:
MyApp.run! :host => 'localhost', :port => 9090Методы, доступные Sinatra::Base сабклассам идентичны тем, что доступны в DSL верхнего уровня. Большинство приложений верхнего уровня могут быть конвертированы в Sinatra::Base компоненты с помощью двух модификаций:
-
Вы должны подключать
sinatra/baseвместоsinatra, иначе все методы предоставляемые Sinatra будут импортированные в глобальное пространство имен. -
Поместите все маршруты, обработчики ошибок, фильтры и опции в сабкласс Sinatra::Base.
Sinatra::Base — это чистый лист. Большинство опций, включая встроенный сервер, по умолчанию отключены. Смотрите Опции и Конфигурация для детальной информации об опциях и их поведении.
Не только сама Sinatra может использовать подпрограммы Rack, любое Sinatra приложение само может быть добавлено к любому Rack эндпоинту в качестве подпрограммы. Этим эндпоинтом может быть другое Sinatra приложение, или приложение, основанное на Rack (Rails/Ramaze/Camping/…).
require 'sinatra/base'
class LoginScreen < Sinatra::Base
enable :sessions
get('/login') { haml :login }
post('/login') do
if params[:name] = 'admin' and params[:password] = 'admin'
session['user_name'] = params[:name]
else
redirect '/login'
end
end
end
class MyApp < Sinatra::Base
# подпрограмма будет запущена перед фильтрами
use LoginScreen
before do
unless session['user_name']
halt "Access denied, please <a href='/login'>login</a>."
end
end
get('/') { "Hello #{session['user_name']}." }
endТекущая область видимости определяет методы и переменные, доступные в данный момент.
Любое Sinatra приложение соответствует сабклассу Sinatra::Base. Если вы используете DSL верхнего уровня (require 'sinatra'), то этим классом будет Sinatra::Application, иначе это будет сабкласс, который вы создали вручную. На уровне класса вам будут доступны такие методы, как get или before, но вы не сможете иметь доступ к объектам request или session, так как существует только единый класс приложения для всех запросов.
Опции, созданные с помощью set, являются методами уровня класса:
class MyApp < Sinatra::Base
# Я в области видимости приложения!
set :foo, 42
foo # => 42
get '/foo' do
# Я больше не в области видимости приложения!
end
endУ вас будет область видимости приложения внутри:
-
Тела вашего класса приложения
-
Методов, определенных расширениями
-
Блока, переданного в
helpers -
Блоков, использованных как значения для
set
Вы можете получить доступ к объекту области видимости (классу приложения) следующими способами:
-
объект, переданный блокам конфигурации (
configure { |c| ... }) -
settingsвнутри области видимости запроса
Для каждого входящего запроса будет создан новый экземпляр вашего приложения, и все блоки обработчика будут запущены в этом контексте. В этой области видимости вам доступны request и session объекты, вызовы методов рендеринга, такие как erb или haml. Вы можете получить доступ к области видимости приложения из контекста запроса, используя помощник settings:
class MyApp < Sinatra::Base
# Я в области видимости приложения!
get '/define_route/:name' do
# Область видимости запроса '/define_route/:name'
@value = 42
settings.get("/#{params[:name]}") do
# Область видимости запроса "/#{params[:name]}"
@value # => nil (другой запрос)
end
"Route defined!"
end
endУ вас будет область видимости запроса внутри:
-
get/head/post/put/delete/options блоков
-
before/after фильтрах
-
методах помощниках
-
шаблонах/видах
Область видимости делегирования просто перенаправляет методы в область видимости класса. Однако, оно не полностью на 100% ведет себя как область видимости класса, так как у вас нету привязки к классу: только методы, явно помеченные для делегирования, будут доступны, а переменных/состояний области видимости класса не будет (иначе говоря, у вас будет другой self объект). Вы можете непосредственно добавить методы делегирования, используя Sinatra::Delegator.delegate :method_name.
У вас будет контекст делегирования внутри:
-
Привязки верхнего уровня, если вы сделали
require "sinatra" -
Объекта, расширенного с помощью примеси
Sinatra::Delegator
Посмотрите сами в код: тут Sinatra::Delegator примесь будет включена в глобальное пространство имен.
Sinatra приложения могут быть запущены напрямую:
ruby myapp.rb [-h] [-x] [-e ENVIRONMENT] [-p PORT] [-o HOST] [-s HANDLER]Опции включают:
-h # помощь
-p # настроить порт (по умолчанию 4567)
-o # настроить хост (по умолчанию 0.0.0.0)
-e # настроить окружение, режим (по умолчанию development)
-s # настроить rack сервер/обработчик (по умолчанию thin)
-x # включить мьютекс (по умолчанию выключен)Если вы хотите использовать новейший код Sinatra, то создайте локальный клон и запускайте свое приложение с sinatra/lib директорией в LOAD_PATH:
cd myapp
git clone git://github.com/sinatra/sinatra.git
ruby -Isinatra/lib myapp.rbТакже вы можете добавить sinatra/lib директорию в LOAD_PATH приложения:
$LOAD_PATH.unshift File.dirname(__FILE__) + '/sinatra/lib'
require 'rubygems'
require 'sinatra'
get '/about' do
"I'm running version " + Sinatra::VERSION
endЧтобы обновить исходники Sinatra:
cd myproject/sinatra
git pull-
Вебсайт проекта - Дополнительная документация, новости и ссылки на другие ресурсы.
-
Участие - Нашли баг? Нужна помощь? Написали патч?
-
IRC: #sinatra on freenode.net