README.ru.rdoc

Path: README.ru.rdoc
Last Update: Tue Dec 06 03:36:02 +0000 2011

Sinatra

Внимание: Этот документ является переводом английской версии и может быть устаревшим

Sinatra — это предметно-ориентированный каркас (DSL) для быстрого создания функциональных веб-приложений на Ruby:

  # myapp.rb
  require 'sinatra'

  get '/' do
    'Hello world!'
  end

Установите gem:

  gem install sinatra

и запустите приложение с помощью:

  ruby -rubygems myapp.rb

Результат можно увидеть: localhost:4567

Рекомендуется также установить thin, сделать это можно командой: gem install thin. Thin - это более производительный и функциональный сервер для разработки приложений на Sinatra.

Маршруты

В Sinatra маршрут — это пара: <HTTP метод> и <шаблон URL>. Каждый маршрут ассоциирован с блоком кода, исполняемого внутри, пример:

  get '/' do
    .. что-то показать ..
  end

  post '/' do
    .. что-то создать ..
  end

  put '/' do
    .. что-то заменить ..
  end

  patch '/' 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 (или ’*’ маску, обозначающую любой символ) параметры доступные в массиве 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

Или с параметрами блока:

  get '/download/*.*' do |path, ext|
    [path, ext] # => ["path/to/file", "xml"]
  end

Маршруты также могут содержать регулярные выражения:

  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 body, либо кодом состояния 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 }

Собственные детекторы совпадений для маршрутов

Как показано выше, Sinatra поставляется со встроенной поддержкой строк и регулярных выражений в качестве шаблонов URL. Но и это еще не все. Вы можете легко определить свои собственные детекторы совпадений (matchers) для маршрутов:

  class AllButPattern
    Match = Struct.new(:captures)

    def initialize(except)
      @except   = except
      @captures = Match.new([])
    end

    def match(str)
      @captures unless @except === str
    end
  end

  def all_but(pattern)
    AllButPattern.new(pattern)
  end

  get all_but("/index") do
    # ...
  end

Заметьте, что предыдущий пример возможно чересчур сложен, он также может быть представлен как:

  get // do
    pass if request.path_info == "/index"
    # ...
  end

Или с использованием негативного просмотра вперед:

  get %r{^(?!/index$)} do
    # ...
  end

Статические файлы

Статические файлы отдаются из ./public директории. Вы можете указать другое место, указав его через опцию :public_folder:

  set :public_folder, File.dirname(__FILE__) + '/static'

Учтите, что имя директории со статическими файлами не включено в URL. Например, файл ./public/css/style.css будет доступен как example.com/css/style.css.

Используйте опцию :static_cache_control (см. ниже), чтобы добавить заголовок Cache-Control.

Представления / Шаблоны

Каждый шаблонизатор представлен своим собственным методом. Эти методы попросту возвращают строку:

  get '/' do
    erb :index
  end

Отобразит views/index.erb.

Вместо имени шаблона вы так же можете передавать непосредственно само содержимое шаблона

  get '/' do
    code = "<%= Time.now >"
    erb code
  end

Эти методы принимают второй аргумент, хеш с опциями:

  get '/' do
    erb :index, :layout => :post
  end

Отобразит views/index.erb, вложенным в views/post.erb (по умолчанию: views/layout.erb, если существует).

Любые опции, не понимаемые Sinatra, будут переданы в шаблонизатор

  get '/' do
    haml :index, :format => :html5
  end

Вы также можете задавать опции для шаблонизаторов в общем:

  set :haml, :format => :html5

  get '/' do
    haml :index
  end

Опции, переданные в метод, переопределяют опции, заданные с помощью set.

Доступные опции:

locals
Список локальных переменных, передаваемых в документ. Например: erb "<%= foo %>", :locals => {:foo => "bar"}
default_encoding
Кодировка, которую следует использовать, если не удалось определить оригинальную. По умолчанию: settings.default_encoding.
views
Директория с шаблонами. По умолчанию: settings.views.
layout
Использовать или нет лэйаут (true или false). Если же значение Symbol, то указывает, какой шаблон использовать в качестве лэйаута. Например: erb :index, :layout => !request.xhr?
content_type
Content-Type отображенного шаблона. По умолчанию: задается шаблонизатором.
scope
Область видимости, в которой рендерятся шаблоны. По умолчанию: экземпляр приложения. Если вы измените эту опцию, то переменные экземпляра и методы-помощники станут недоступными в ваших шаблонах.
layout_engine
Шаблонизатор, который следует использовать для отображения лэйаута. Полезная опция для шаблонизаторов, в которых нет никакой поддержки лэйаутов. По умолчанию: тот же шаблонизатор, что используется и для самого шаблона. Пример: set :rdoc, :layout_engine => :erb

По умолчанию считается, что шаблоны находятся в директории ./views. Чтобы использовать другую директорию с шаблонами:

  set :views, settings.root + '/templates'

Важное замечание: вы всегда должны ссылаться на шаблоны с помощью символов (Symbol), даже когда они в поддиректории (в этом случае используйте :’subdir/template‘). Вы должны использовать символы, потому что иначе шаблонизаторы попросту отображают любые строки, переданные им.

Доступные шаблонизаторы

Некоторые языки шаблонов имеют несколько реализаций. Чтобы указать, какую реализацию использовать, вам следует просто подключить нужную библиотеку:

  require 'rdiscount' # или require 'bluecloth'
  get('/') { markdown :index }

Haml шаблоны

Зависимости:haml
Расширения файлов:.haml
Пример:haml :index, :format => :html5

Erb шаблоны

Зависимости:erubis или erb (включен в Ruby)
Расширения файлов:.erb, .rhtml or .erubis (только Erubis)
Пример:erb :index

Builder шаблоны

Зависимости:builder
Расширения файлов:.builder
Пример:builder { |xml| xml.em "hi" }

Блок также используется и для встроенных шаблонов (см. пример).

Nokogiri шаблоны

Зависимости:nokogiri
Расширения файлов:.nokogiri
Пример:nokogiri { |xml| xml.em "hi" }

Блок также используется и для встроенных шаблонов (см. пример).

Sass шаблоны

Зависимости:sass
Расширения файлов:.sass
Пример:sass :stylesheet, :style => :expanded

SCSS шаблоны

Зависимости:sass
Расширения файлов:.scss
Пример:scss :stylesheet, :style => :expanded

Less шаблоны

Зависимости:less
Расширения файлов:.less
Пример:less :stylesheet

Liquid шаблоны

Зависимости:liquid
Расширения файлов:.liquid
Пример:liquid :index, :locals => { :key => ‘value’ }

Так как в Liquid шаблонах невозможно вызывать методы из Ruby (кроме yield), то вы почти всегда будете передавать в шаблон локальные переменные.

Markdown шаблоны

Зависимости:rdiscount, redcarpet, bluecloth, kramdown или maruku
Расширения файлов:.markdown, .mkd and .md
Пример:markdown :index, :layout_engine => :erb

В Markdown невозможно вызывать методы или передавать локальные переменные. Следовательно, вам, скорее всего, придется использовать этот шаблон совместно с другим шаблонизатором:

  erb :overview, :locals => { :text => markdown(:introduction) }

Заметьте, что вы можете вызывать метод markdown из других шаблонов:

  %h1 Hello From Haml!
  %p= markdown(:greetings)

Вы не можете вызывать Ruby из Markdown, соответственно, вы не можете использовать лэйауты на Markdown. Тем не менее, есть возможность использовать один шаблонизатор для отображения шаблона, а другой для лэйаута с помощью опции :layout_engine.

Textile шаблоны

Зависимости:RedCloth
Расширения файлов:.textile
Пример:textile :index, :layout_engine => :erb

В Textile невозможно вызывать методы или передавать локальные переменные. Следовательно, вам, скорее всего, придется использовать этот шаблон совместно с другим шаблонизатором:

  erb :overview, :locals => { :text => textile(:introduction) }

Заметьте, что вы можете вызывать метод textile из других шаблонов:

  %h1 Hello From Haml!
  %p= textile(:greetings)

Вы не можете вызывать Ruby из Textile, соответственно, вы не можете использовать лэйауты на Textile. Тем не менее, есть возможность использовать один шаблонизатор для отображения шаблона, а другой для лэйаута с помощью опции :layout_engine.

RDoc шаблоны

Зависимости:rdoc
Расширения файлов:.rdoc
Пример:textile :README, :layout_engine => :erb

В RDoc невозможно вызывать методы или передавать локальные переменные. Следовательно, вам, скорее всего, придется использовать этот шаблон совместно с другим шаблонизатором:

  erb :overview, :locals => { :text => rdoc(:introduction) }

Заметьте, что вы можете вызывать метод rdoc из других шаблонов:

  %h1 Hello From Haml!
  %p= rdoc(:greetings)

Вы не можете вызывать Ruby из RDoc, соответственно, вы не можете использовать лэйауты на RDoc. Тем не менее, есть возможность использовать один шаблонизатор для отображения шаблона, а другой для лэйаута с помощью опции :layout_engine.

Radius шаблоны

Зависимости:radius
Расширения файлов:.radius
Пример:radius :index, :locals => { :key => ‘value’ }

Так как в Radius шаблонах невозможно вызывать методы из Ruby напрямую, то вы почти всегда будете передавать в шаблон локальные переменные.

Markaby шаблоны

Зависимости:markaby
Расширения файлов:.mab
Пример:markaby { h1 "Welcome!" }

Блок также используется и для встроенных шаблонов (см. пример).

Slim шаблоны

Зависимости:slim
Расширения файлов:.slim
Пример:slim :index

Creole шаблоны

Зависимости:creole
Расширения файлов:.creole
Пример:creole :wiki, :layout_engine => :erb

В Creole невозможно вызывать методы или передавать локальные переменные. Следовательно, вам, скорее всего, придется использовать этот шаблон совместно с другим шаблонизатором:

  erb :overview, :locals => { :text => creole(:introduction) }

Заметьте, что вы можете вызывать метод creole из других шаблонов:

  %h1 Hello From Haml!
  %p= creole(:greetings)

Вы не можете вызывать Ruby из Creole, соответственно, вы не можете использовать лэйауты на Creole. Тем не менее, есть возможность использовать один шаблонизатор для отображения шаблона, а другой для лэйаута с помощью опции :layout_engine.

CoffeeScript шаблоны

Зависимости:coffee-script и способ запускать javascript
Расширения файлов:.coffee
Пример:coffee :index

Встроенные шаблоны

  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= bar.name', :locals => { :bar => foo }
  end

Это обычный подход, когда шаблоны рендерятся как части других шаблонов.

Вложенные шаблоны

Шаблоны также могут быть определены в конце исходного файла:

  require 'sinatra'

  get '/' do
    haml :index
  end

  __END__

  @@ layout
  %html
    = yield

  @@ index
  %div.title Hello world!!!!!

Заметьте: вложенные шаблоны, определенные в исходном файле, который подключила 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 => false или отключить его для всего приложения, например, так: set :haml, :layout => false:

  get '/' do
    haml :index, :layout => !request.xhr?
  end

Привязка файловых расширений

Чтобы связать расширение файла с движком рендеринга, используйте Tilt.register. Например, если вы хотите использовать расширение tt для шаблонов Textile:

  Tilt.register :tt, Tilt[:textile]

Добавление собственного движка рендеринга

Сначала зарегистрируйте свой движок в Tilt, затем создайте метод, отвечающий за отрисовку:

  Tilt.register :myat, MyAwesomeTemplateEngine

  helpers do
    def myat(*args) render(:myat, *args) end
  end

  get '/' do
    myat :index
  end

Отобразит ./views/index.myat. Чтобы узнать больше о Tilt, смотрите github.com/rtomayko/tilt

Фильтры

before-фильтры выполняются перед каждым запросом в том же контексте, что и маршруты. Фильтры могут изменять как запрос, так и ответ на него. Переменные экземпляра, установленные в фильтрах, доступны в маршрутах и шаблонах:

  before do
    @note = 'Hi!'
    request.path_info = '/foo/bar/baz'
  end

  get '/foo/*' do
    @note #=> 'Hi!'
    params[:splat] #=> 'bar/baz'
  end

after-фильтры выполняются после каждого запроса в том же контексте, что и пути. Фильтры могут изменять как запрос, так и ответ на него. Переменные экземпляра, установленные в before-фильтрах и маршрутах, будут доступны в after-фильтрах:

  after do
    puts response.status
  end

Заметьте: если вы используете метод body, а не просто возвращаете строку из маршрута, то тело ответа не будет доступно в after-фильтрах, так как оно будет сгенерировано позднее.

Фильтры могут использовать шаблоны 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

Методы-помощники

Используйте метод helpers, чтобы определить методы-помощники, которые в дальнейшем можно будет использовать в обработчиках маршрутов и шаблонах:

  helpers do
    def bar(name)
      "#{name}bar"
    end
  end

  get '/:name' do
    bar(params[:name])
  end

Использование сессий

Сессия используется, чтобы сохранять состояние между запросами. Если эта опция включена, то у вас будет один хеш сессии на одну пользовательскую сессию:

  enable :sessions

  get '/' do
    "value = " << session[:value].inspect
  end

  get '/:value' do
    session[:value] = params[:value]
  end

Заметьте, что при использовании enable :sessions все данные сохраняются в cookies. Это может быть не совсем то, что вы хотите (например, сохранение больших объемов данных увеличит ваш трафик). В таком случае вы можете использовать альтернативную Rack "прослойку" (middleware), реализующую механизм сессий. Для этого *не надо* вызывать enable :sessions, вместо этого следует подключить ее так же, как и любую другую "прослойку":

  use Rack::Session::Pool, :expire_after => 2592000

  get '/' do
    "value = " << session[:value].inspect
  end

  get '/:value' do
    session[:value] = params[:value]
  end

Для повышения безопасности данные сессии в куках подписываются секретным ключом. Секретный ключ генерируется Sinatra. Тем не менее, так как этот ключ будет меняться с каждым запуском приложения, вы, возможно, захотите установить ключ вручную, чтобы у всех экземпляров вашего приложения был один и тот же ключ:

  set :session_secret, 'super secret'

Если вы хотите больше настроек для сессий, вы можете задать их, передав хеш опций в параметр sessions:

  set :sessions, :domain => 'foo.com'

Прерывание

Чтобы незамедлительно прервать обработку запроса внутри фильтра или маршрута, используйте:

  halt

Можно также указать статус при прерывании:

  halt 410

Тело:

  halt 'this will be the body'

И то, и другое:

  halt 401, 'go away!'

Можно указать заголовки:

  halt 402, {'Content-Type' => 'text/plain'}, 'revenge'

И, конечно, можно использовать шаблоны с halt:

  halt erb(:error)

Передача

Маршрут может передать обработку запроса следующему совпадающему маршруту, используя pass:

  get '/guess/:who' do
    pass unless params[:who] == 'Frank'
    'You got me!'
  end

  get '/guess/*' do
    'You missed!'
  end

Блок маршрута сразу же прерывается, и контроль переходит к следующему совпадающему маршруту. Если соответствующий маршрут не найден, то ответом на запрос будет 404.

Вызов другого маршрута

Иногда pass не подходит, например, если вы хотите получить результат вызова другого обработчика маршрута. В таком случае просто используйте call:

  get '/foo' do
    status, headers, body = call env.merge("PATH_INFO" => '/bar')
    [status, headers, body.map(&:upcase)]
  end

  get '/bar' do
    "bar"
  end

Заметьте, что в предыдущем примере можно облегчить тестирование и повысить производительность, перенеся "bar" в метод-помощник, используемый и в /foo, и в /bar.

Если вы хотите, чтобы запрос был отправлен в тот же экземпляр приложения, а не в его копию, используйте call! вместо call.

Если хотите узнать больше о call, смотрите спецификацию Rack.

Задание тела, кода и заголовков ответа

Хорошим тоном является установка кода состояния HTTP и тела ответа в возвращаемом значении обработчика маршрута. Тем не менее, в некоторых ситуациях вам, возможно, понадобится задать тело ответа в произвольной точке потока исполнения. Вы можете сделать это с помощью метода-помощника body. Если вы задействуете метод body, то вы можете использовать его и в дальнейшем, чтобы получить доступ к телу ответа.

  get '/foo' do
    body "bar"
  end

  after do
    puts body
  end

Также можно передать блок в метод body, который затем будет вызван обработчиком Rack (такой подход может быть использован для реализации поточного ответа, см. "Возвращаемые значения").

Аналогично вы можете установить код ответа и его заголовки:

  get '/foo' do
    status 418
    headers \
      "Allow"   => "BREW, POST, GET, PROPFIND, WHEN",
      "Refresh" => "Refresh: 20; http://www.ietf.org/rfc/rfc2324.txt"
    body "I'm a tea pot!"
  end

Как и body, методы headers и status, вызванные без аргументов, возвращают свои текущие значения.

Логирование

В области видимости запроса метод logger предоставляет доступ к экземпляру Logger:

  get '/' do
    logger.info "loading data"
    # ...
  end

Этот логер автоматически учитывает ваши настройки логирования в Rack. Если логирование выключено, то этот метод вернет пустой (dummy) объект, поэтому вы можете смело использовать его в маршрутах и фильтрах.

Заметьте, что логирование включено по умолчанию только для Sinatra::Application, а если ваше приложение — подкласс Sinatra::Base, то вы, наверное, захотите включить его вручную:

  class MyApp < Sinatra::Base
    configure(:production, :development) do
      enable :logging
    end
  end

Mime-типы

Когда вы используете send_file или статические файлы, у вас могут быть mime-типы, которые Sinatra не понимает по умолчанию. Используйте mime_type для их регистрации по расширению файла:

  configure do
    mime_type :foo, 'text/foo'
  end

Вы также можете использовать это в content_type методе-помощнике:

  get '/' do
    content_type :foo
    "foo foo foo"
  end

Генерирование URL

Чтобы сформировать URL вам следует использовать метод url, например, в Haml:

  %a{:href => url('/foo')} foo

Этот метод учитывает обратные прокси и маршрутизаторы Rack, если они присутствуют.

Наряду с url вы можете использовать to (смотрите пример ниже).

Перенаправление (редирект)

Вы можете перенаправить браузер пользователя с помощью метода redirect:

  get '/foo' do
    redirect to('/bar')
  end

Любые дополнительные параметры используются по аналогии с аргументами метода halt:

  redirect to('/bar'), 303
  redirect 'http://google.com', 'wrong place, buddy'

Вы также можете перенаправить пользователя обратно, на страницу с которой он пришел, с помощью redirect back:

  get '/foo' do
    "<a href='/bar'>do something</a>"
  end

  get '/bar' do
    do_something
    redirect back
  end

Чтобы передать какие-либо параметры вместе с перенаправлением, либо добавьте их в строку запроса:

  redirect to('/bar?sum=42')

либо используйте сессию:

  enable :sessions

  get '/foo' do
    session[:secret] = 'foo'
    redirect to('/bar')
  end

  get '/bar' do
    session[:secret]
  end

Управление кэшированием

Установка корректных заголовков — основа правильного HTTP кэширования.

Вы можете легко выставить заголовок Cache-Control таким образом:

  get '/' do
    cache_control :public
    "cache it!"
  end

Совет: задавайте кэширование в before-фильтре:

  before do
    cache_control :public, :must_revalidate, :max_age => 60
  end

Если вы используете метод expires для задания соответствующего заголовка, то Cache-Control будет выставлен автоматически:

  before do
    expires 500, :public, :must_revalidate
  end

Чтобы как следует использовать кэширование, вам следует подумать об использовании etag и last_modified. Рекомендуется использовать эти методы-помощники *до* выполнения ресурсоемких вычислений, так как они немедленно отправят ответ клиенту, если текущая версия уже есть в их кэше:

  get '/article/:id' do
    @article = Article.find params[:id]
    last_modified @article.updated_at
    etag @article.sha1
    erb :article
  end

Также вы можете использовать weak ETag:

  etag @article.sha1, :weak

Эти методы-помощники не станут ничего кэшировать для вас, но они дадут необходимую информацию для вашего кэша. Если вы ищите легкое решение для кэширования, попробуйте rack-cache:

  require 'rack/cache'
  require 'sinatra'

  use Rack::Cache

  get '/' do
    cache_control :public, :max_age => 36000
    sleep 5
    "hello"
  end

Используйте опцию :static_cache_control (см. ниже), чтобы добавить заголовок Cache-Control к статическим файлам.

Отправка файлов

Для отправки файлов пользователю вы можете использовать метод send_file:

  get '/' do
    send_file 'foo.png'
  end

Этот метод имеет несколько опций:

  send_file 'foo.png', :type => :jpg

Возможные опции:

filename
имя файла, по умолчанию: реальное имя файла.
last_modified
значение для заголовка Last-Modified, по умолчанию: mtime (время изменения) файла.
type
тип файла, по умолчанию: предполагается по расширению файла.
disposition
используется для заголовка Content-Disposition, возможные значения: nil (по умолчанию), :attachment и :inline.
length
значения для заголовка Content-Length, по умолчанию: размер файла.

Этот метод будет использовать возможности Rack сервера для отправки файлов, если они доступны, а в противном случае, будет напрямую отдавать файл из Ruby процесса. Метод send_file также обеспечивает автоматическую обработку частичных (range) запросов с помощью Sinatra.

Доступ к объекту запроса

Объект входящего запроса доступен на уровне обработки запроса (в фильтрах, маршрутах, обработчиках ошибок) с помощью request метода:

  # приложение запущено на http://example.com/example
  get '/foo' do
    t = %w[text/css text/html application/javascript]
    request.accept              # ['text/html', '*/*']
    request.accept? 'text/xml'  # true
    request.preferred_type(t)   # 'text/html'
    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.referrer            # источник запроса клиента либо '/'
    request.user_agent          # user agent (используется для :agent условия)
    request.cookies             # хеш, содержащий cookies браузера
    request.xhr?                # является ли запрос ajax запросом?
    request.url                 # "http://example.com/example/foo"
    request.path                # "/example/foo"
    request.ip                  # IP-адрес клиента
    request.secure?             # false (true, если запрос сделан через SSL)
    request.forwarded?          # true (если сервер работает за обратным прокси)
    request.env                 # "сырой" env хеш, полученный Rack
  end

Некоторые опции, такие как script_name или path_info доступны для изменения:

  before { request.path_info = "/" }

  get "/" do
    "all requests end up here"
  end

request.body является IO или StringIO объектом:

  post "/api" do
    request.body.rewind  # в случае, если кто-то уже прочитал тело запроса
    data = JSON.parse request.body.read
    "Hello #{data['name']}!"
  end

Вложения

Вы можете использовать метод attachment, чтобы сказать браузеру, что ответ сервера должен быть сохранен на диск, а не отображен:

  get '/' do
    attachment
    "store it!"
  end

Вы также можете указать имя файла:

  get '/' do
    attachment "info.txt"
    "store it!"
  end

Поиск шаблонов

Для поиска шаблонов и их последующего рендеринга используется метод find_template:

  find_template settings.views, 'foo', Tilt[:haml] do |file|
    puts "could be #{file}"
  end

Это не слишком полезный пример. Зато полезен тот факт, что вы можете переопределить этот метод, чтобы использовать свой собственный механизм поиска. Например, если вы хотите, чтобы можно было использовать несколько директорий с шаблонами:

  set :views, ['views', 'templates']

  helpers do
    def find_template(views, name, engine, &block)
      Array(views).each { |v| super(v, name, engine, &block) }
    end
  end

Другой пример, в котором используются разные директории для движков рендеринга:

  set :views, :sass => 'views/sass', :haml => 'templates', :default => 'views'

  helpers do
    def find_template(views, name, engine, &block)
      _, folder = views.detect { |k,v| engine == Tilt[k] }
      folder ||= views[:default]
      super(folder, name, engine, &block)
    end
  end

Вы можете легко вынести этот код в расширение и поделиться им с остальными!

Заметьте, что find_template не проверяет, существует ли файл на самом деле, а вызывает заданный блок для всех возможных путей. Дело тут не в производительности, дело в том, что render вызовет break, как только файл не будет найден. Содержимое и местонахождение шаблонов будет закэшировано, если приложение запущено не в режиме разработки (set :environment, :development). Вы должны помнить об этих нюансах, если пишите по-настоящему "сумасшедший" метод.

Конфигурация

Этот блок исполняется один раз при старте в любом окружении, режиме (environment):

  configure do
    # задание одной опции
    set :option, 'value'

    # устанавливаем несколько опций
    set :a => 1, :b => 2

    # то же самое, что и `set :option, true`
    enable :option

    # то же самое, что и `set :option, false`
    disable :option

    # у вас могут быть "динамические" опции с блоками
    set(:css_dir) { File.join(views, 'css') }
  end

Будет запущено, когда окружение (RACK_ENV переменная) :production:

  configure :production do
    ...
  end

Будет запущено, когда окружение :production или :test:

  configure :production, :test do
    ...
  end

Вы можете получить доступ к этим опциям с помощью settings:

  configure do
    set :foo, 'bar'
  end

  get '/' do
    settings.foo? # => true
    settings.foo  # => 'bar'
    ...
  end

Доступные настройки

absolute_redirects
если отключено, то Sinatra будет позволять использование относительных перенаправлений, но при этом перестанет соответствовать RFC 2616 (HTTP 1.1), который разрешает только абсолютные перенаправления.

Включайте эту опцию, если ваше приложение работает за обратным прокси, который настроен не совсем корректно. Обратите внимание, метод url все равно будет генерировать абсолютные URL, если вы не передадите false вторым аргументом.

Отключено по умолчанию.

add_charsets
mime-типы, к которым метод content_type будет автоматически добавлять информацию о кодировке.

Вам следует добавлять значения к этой опции вместо ее переопределения:

  settings.add_charsets << "application/foobar"
app_file
главный файл приложения, используется для определения корневой директории проекта, директорий с шаблонами и статическими файлами, вложенных шаблонов.
bind
используемый IP-адрес (по умолчанию: 0.0.0.0). Используется только встроенным сервером.
default_encoding
кодировка, если неизвестна (по умолчанию: "utf-8").
dump_errors
отображать ошибки в логе.
environment
текущее окружение, по умолчанию, значение ENV[‘RACK_ENV’] или "development", если ENV[‘RACK_ENV’] не доступна.
logging
использовать логер.
lock
создает блокировку для каждого запроса, которая гарантирует обработку только одного запроса в текущий момент времени в Ruby процессе.

Включайте, если ваше приложение не потоко-безопасно (thread-safe). Отключено по умолчанию.

method_override
использовать "магический" параметр _method, чтобы позволить использование PUT/DELETE форм в браузерах, которые не поддерживают эти методы.
port
порт, на котором будет работать сервер. Используется только встроенным сервером.
prefixed_redirects
добавлять или нет параметр request.script_name к редиректам, если не задан абсолютный путь. Таким образом, redirect ’/foo‘ будет вести себя как redirect to(’/foo’). Отключено по умолчанию.
public_folder
директория, откуда будут раздаваться статические файлы.
reload_templates
перезагружать или нет шаблоны на каждый запрос. Включено в режиме разработки.
root
корневая директория проекта.
raise_errors
выбрасывать исключения (будет останавливать приложение).
run
если включено, Sinatra будет самостоятельно запускать веб-сервер. Не включайте, если используете rackup или аналогичные средства.
running
работает ли сейчас встроенный сервер? Не меняйте эту опцию!
server
сервер или список серверов, которые следует использовать в качестве встроенного сервера. По умолчанию: [‘thin’, ‘mongrel’, ‘webrick’], порядок задает приоритет.
sessions
включить сессии на основе кук (cookie).
show_exceptions
показывать исключения/стек вызовов (stack trace) в браузере.
static
должна ли Sinatra осуществлять раздачу статических файлов. Отключите, когда используете какой-либо веб-сервер для этой цели. Отключение значительно улучшит производительность приложения. По умолчанию включено в классических и отключено в модульных приложениях.
static_cache_control
Когда Sinatra отдает статические файлы, используйте эту опцию, чтобы добавить им заголовок Cache-Control. Для этого используется метод-помощник cache_control. По умолчанию отключено. Используйте массив, когда надо задать несколько значений: set :static_cache_control, [:public, :max_age => 300]
views
директория с шаблонами.

Обработка ошибок

Обработчики ошибок исполняются в том же контексте, что и маршруты, и before-фильтры, а это означает, что всякие прелести вроде haml, erb, halt и т.д. доступны и им.

Not Found

Когда выброшено исключение 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...' + 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'
  end

Sinatra устанавливает специальные not_found и error обработчики, когда приложение запущено в режиме разработки (окружение :development).

Rack "прослойки"

Sinatra использует Rack, минимальный стандартный интерфейс для веб-фреймворков на Ruby. Одной из самых интересных для разработчиков возможностей Rack является поддержка "прослоек" ("middleware") — компонентов, находящихся "между" сервером и вашим приложением, которые отслеживают и/или манипулируют HTTP запросами/ответами для предоставления различной функциональности.

В Sinatra очень просто использовать такие "прослойки" с помощью метода 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'
  end

Rack распространяется с различными стандартными "прослойками" для логирования, отладки, маршрутизации URL, аутентификации, обработки сессий. Sinatra использует многие из этих компонентов автоматически, основываясь на конфигурации, чтобы вам не приходилось подключать (use) их вручную.

Вы можете найти полезные прослойки в rack, rack-contrib, CodeRack или в Rack wiki.

Тестирование

Тесты для 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::Base — "прослойки", библиотеки и модульные приложения

Описание своего приложения самым простейшим способом (с помощью 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
  end

Методы, доступные Sinatra::Base подклассам идентичны тем, что доступны в DSL верхнего уровня. Большинство приложений верхнего уровня могут быть конвертированы в Sinatra::Base компоненты с помощью двух модификаций:

  • Вы должны подключать sinatra/base вместо sinatra, иначе все методы, предоставляемые Sinatra, будут импортированы в глобальное пространство имен.
  • Поместите все маршруты, обработчики ошибок, фильтры и опции в подкласс Sinatra::Base.

Sinatra::Base — это чистый лист. Большинство опций, включая встроенный сервер, по умолчанию отключены. Смотрите Опции и конфигурация для детальной информации об опциях и их поведении.

Модульные приложения против классических

Вопреки всеобщему убеждению, в классическом стиле (самом простом) нет ничего плохого. Если этот стиль подходит вашему приложению, вы не обязаны переписывать его в модульное приложение.

У классического стиля есть всего два недостатка относительно модульного:

  • У вас может быть только одно приложение Sinatra на один Ruby процесс. Если вы планируете использовать больше, то переключайтесь на модульный стиль.
  • Приложения, использующие классический стиль, добавляют методы к Object. Если вы планируете поставлять свое приложение в виде библиотеки/gem, то переходите на модульный стиль.

Не существует причин, по которым вы не могли бы смешивать модульный и классический стили.

Переходя с одного стиля на другой, примите во внимание следующие изменения в настройках:

  Опция               Классический            Модульный

  app_file            файл с приложением      файл с подклассом Sinatra::Base
  run                 $0 == app_file          false
  logging             true                    false
  method_override     true                    false
  inline_templates    true                    false
  static              true                    false

Запуск модульных приложений

Есть два общепринятых способа запускать модульные приложения: запуск напрямую с помощью run!:

  # my_app.rb
  require 'sinatra/base'

  class MyApp < Sinatra::Base
    # ... здесь код приложения ...

    # запускаем сервер, если исполняется текущий файл
    run! if app_file == $0
  end

И запускаем с помощью:

  ruby my_app.rb

Или с помощью конфигурационного файла config.ru, который позволяет использовать любой Rack-совместимый сервер приложений.

  # config.ru
  require './my_app'
  run MyApp

Запускаем:

  rackup -p 4567

Запуск классических приложений с config.ru

Файл приложения:

  # app.rb
  require 'sinatra'

  get '/' do
    'Hello world!'
  end

И соответствующий config.ru:

  require './app'
  run Sinatra::Application

Когда использовать config.ru?

Вот несколько причин, по которым вы, возможно, захотите использовать config.ru:

  • вы хотите разворачивать свое приложение на различных Rack-совместимых серверах (Passenger, Unicorn, Heroku, …);
  • вы хотите использовать более одного подкласса Sinatra::Base;
  • вы хотите использовать Sinatra только в качестве "прослойки" Rack.

Совсем необязательно переходить на использование config.ru лишь потому, что вы стали использовать модульный стиль приложения. И необязательно использовать модульный стиль, чтобы запускать приложение с помощью config.ru.

Использование Sinatra в качестве "прослойки"

Не только сама Sinatra может использовать "прослойки" Rack, но и любое Sinatra приложение само может быть добавлено к любому Rack endpoint в качестве "прослойки". Этим endpoint (конечной точкой) может быть другое 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' && 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.new:

  require 'sinatra/base'
  my_app = Sinatra.new { get('/') { "hi" } }
  my_app.run!

Этот метод может принимать аргументом приложение, от которого следует наследоваться:

  # config.ru
  require 'sinatra/base'

  controller = Sinatra.new do
    enable :logging
    helpers MyHelpers
  end

  map('/a') do
    run Sinatra.new(controller) { get('/') { 'a' } }
  end

  map('/b') do
    run Sinatra.new(controller) { get('/') { 'b' } }
  end

Это особенно полезно для тестирования расширений Sinatra и при использовании Sinatra внутри вашей библиотеки.

Благодаря этому, использовать Sinatra как "прослойку" очень просто:

  require 'sinatra/base'

  use Sinatra do
    get('/') { ... }
  end

  run RailsProject::Application

Области видимости и привязка

Текущая область видимости определяет методы и переменные, доступные в данный момент.

Область видимости приложения / класса

Любое 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;
  • блока, переданного в Sinatra.new.

Вы можете получить доступ к объекту области видимости (классу приложения) следующими способами:

  • через объект, переданный блокам конфигурации (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 фильтрах;
  • методах-помощниках;
  • шаблонах/отображениях.

Область видимости делегирования

Область видимости делегирования просто перенаправляет методы в область видимости класса. Однако, она не полностью ведет себя как область видимости класса, так как у вас нет привязки к классу. Только методы, явно помеченные для делегирования, будут доступны, а переменных/состояний области видимости класса не будет (иначе говоря, у вас будет другой 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 # включить мьютекс-блокировку (по умолчанию выключена)

Системные требования

Следующие версии Ruby официально поддерживаются:

Ruby 1.8.7
1.8.7 полностью поддерживается, тем не менее, если вас ничто не держит на этой версии, рекомендуем обновиться до 1.9.2 или перейти на JRuby или Rubinius.
Ruby 1.9.2
1.9.2 поддерживается и рекомендована к использованию. Заметьте, что Radius и Markaby пока несовместимы с 1.9.2. Не используйте 1.9.2p0, известно, что эта версия весьма нестабильна при использовании Sinatra.
Rubinius
Rubinius официально поддерживается (Rubinius >= 1.2.3), всё, включая все языки шаблонов, работает.
JRuby
JRuby официально поддерживается (JRuby >= 1.6.1). Нет никаких проблем с использованием альтернативных шаблонов. Тем не менее, если вы выбираете JRuby, то, пожалуйста, посмотрите на JRuby Rack-сервера, так как Thin не поддерживается полностью на JRuby. Поддержка расширений на C в JRuby все еще экспериментальная, что на данный момент затрагивает только RDiscount и Redcarpet.

Ruby 1.8.6 больше не поддерживается. Если вы хотите запускать свое приложение на 1.8.6, откатитесь до Sinatra 1.2, которая будет получать все исправления ошибок до тех пор, пока не будет выпущена Sinatra 1.4.0

Мы также следим за предстоящими к выходу версиями Ruby.

Следующие реализации Ruby не поддерживаются официально, но известно, что на них запускается Sinatra:

  • старые версии JRuby и Rubinius;
  • MacRuby, Maglev, IronRuby;
  • Ruby 1.9.0 и 1.9.1;
  • Ruby 1.8.6 с помощью backports.

То, что версия официально не поддерживается, означает, что, если что-то не работает на этой версии, а на поддерживаемой работает - это не наша проблема, а их.

Мы также запускаем наши CI-тесты на последней версии Ruby (предстоящей 1.9.3), но мы не можем ничего гарантировать, так как она постоянно развивается. Предполагается, что 1.9.3p0 будет поддерживаться.

Sinatra должна работать на любой операционной системе, в которой есть одна из указанных выше версий Ruby.

На острие

Если вы хотите использовать самый последний код Sinatra, не бойтесь запускать свое приложение вместе с кодом из master ветки Sinatra, она весьма стабильна.

Мы также время от времени выпускаем предварительные версии, так что вы можете делать так:

  gem install sinatra --pre

Чтобы воспользоваться некоторыми самыми последними возможностями.

С помощью Bundler

Если вы хотите запускать свое приложение с последней версией Sinatra, то рекомендуем использовать Bundler.

Сначала установите Bundler, если у вас его еще нет:

  gem install bundler

Затем создайте файл Gemfile в директории вашего проекта:

  source :rubygems
  gem 'sinatra', :git => "git://github.com/sinatra/sinatra.git"

  # другие зависимости
  gem 'haml'                    # например, если используете haml
  gem 'activerecord', '~> 3.0'  # может быть, вам нужен и ActiveRecord 3.x

Обратите внимание, вам нужно будет указывать все зависимости вашего приложения в этом файле. Однако, непосредственные зависимости Sinatra (Rack и Tilt) Bundler автоматически скачает и добавит.

Теперь вы можете запускать свое приложение так:

  bundle exec ruby myapp.rb

Вручную

Создайте локальный клон репозитория и запускайте свое приложение с sinatra/lib директорией в $LOAD_PATH:

  cd myapp
  git clone git://github.com/sinatra/sinatra.git
  ruby -Isinatra/lib myapp.rb

Чтобы обновить исходники Sinatra:

  cd myapp/sinatra
  git pull

Установка глобально

Вы можете самостоятельно собрать gem:

  git clone git://github.com/sinatra/sinatra.git
  cd sinatra
  rake sinatra.gemspec
  rake install

Если вы устанавливаете пакеты (gem) от пользователя root, то вашим следующим шагом должна быть команда

  sudo rake install

Версии

Sinatra использует Semantic Versioning, SemVer и SemVerTag.

Дальнейшее чтение

[Validate]