Sinatra

注) 本文書は英語から翻訳したものであり、その内容が最新でない場合もあります。最新の情報はオリジナルの英語版を参照して下さい。

Sinatraは最小の労力でRubyによるWebアプリケーションを手早く作るためのDSLです。

# myapp.rb
require 'sinatra'

get '/' do
  'Hello world!'
end

gemをインストールし、

gem install sinatra

次のように実行します。

ruby myapp.rb

localhost:4567 を開きます。

ThinがあればSinatraはこれを利用するので、gem install thinすることをお薦めします。

目次

ルーティング(Routes)

Sinatraでは、ルーティングはHTTPメソッドとURLマッチングパターンがペアになっています。 ルーティングはブロックに結び付けられています。

get '/' do
  .. 何か見せる ..
end

post '/' do
  .. 何か生成する ..
end

put '/' do
  .. 何か更新する ..
end

patch '/' do
  .. 何か修正する ..
end

delete '/' do
  .. 何か削除する ..
end

options '/' do
  .. 何か満たす ..
end

link '/' do
  .. 何かリンクを張る ..
end

unlink '/' do
  .. 何かアンリンクする ..
end

ルーティングは定義された順番にマッチします。 リクエストに最初にマッチしたルーティングが呼び出されます。

ルーティングのパターンは名前付きパラメータを含むことができ、 paramsハッシュで取得できます。

get '/hello/:name' do
  # "GET /hello/foo" と "GET /hello/bar" にマッチ
  # params[:name] は 'foo' か 'bar'
  "Hello #{params[:name]}!"
end

また、ブロックパラメータで名前付きパラメータにアクセスすることもできます。

get '/hello/:name' do |n|
  # "GET /hello/foo" と "GET /hello/bar" にマッチ
  # params[:name] は 'foo' か 'bar'
  # n が params[:name] を保持
  "Hello #{n}!"
end

ルーティングパターンはアスタリスク(すなわちワイルドカード)を含むこともでき、 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

ルーティングパターンは、オプショナルパラメータを取ることもできます。

get '/posts.?:format?' do
  # "GET /posts" と "GET /posts.json", "GET /posts.xml" の拡張子などにマッチ
end

ところで、ディレクトリトラバーサル攻撃防御設定を無効にしないと(下記参照)、 ルーティングにマッチする前にリクエストパスが修正される可能性があります。

条件(Conditions)

ルーティングにはユーザエージェントのようなさまざまな条件を含めることができます。

get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do
  "Songbirdのバージョン #{params[:agent][0]}を使ってます。"
end

get '/foo' do
  # Songbird以外のブラウザにマッチ
end

ほかにhost_nameprovides条件が利用可能です。

get '/', :host_name => /^admin\./ do
  "Adminエリアです。アクセスを拒否します!"
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
  "あなたの勝ちです!"
end

get '/win_a_car' do
  "残念、あなたの負けです。"
end

複数の値を取る条件には、アスタリスクを使います。

set(:auth) do |*roles|   # <- ここでアスタリスクを使う
  condition do
    unless logged_in? && roles.any? {|role| current_user.in_role? role }
      redirect "/login/", 303
    end
  end
end

get "/my/account/", :auth => [:user, :admin] do
  "アカウントの詳細"
end

get "/only/admin/", :auth => :admin do
  "ここは管理者だけ!"
end

戻り値(Return Values)

ルーティングブロックの戻り値は、HTTPクライアントまたはRackスタックでの次のミドルウェアに渡されるレスポンスボディを決定します。

これは大抵の場合、上の例のように文字列ですが、それ以外の値も使用することができます。

Rackレスポンス、Rackボディオブジェクト、HTTPステータスコードのいずれかとして妥当なオブジェクトであればどのようなオブジェクトでも返すことができます。

  • 3つの要素を含む配列: [ステータス(Fixnum), ヘッダ(Hash), レスポンスボディ(#eachに応答する)]
  • 2つの要素を含む配列: [ステータス(Fixnum), レスポンスボディ(#eachに応答する)]
  • #eachに応答するオブジェクト。通常はそのまま何も返さないが、 与えられたブロックに文字列を渡す。
  • ステータスコードを表現する整数(Fixnum)

これにより、例えばストリーミングを簡単に実装することができます。

class Stream
  def each
    100.times { |i| yield "#{i}\n" }
  end
end

get('/') { Stream.new }

後述するstreamヘルパーメソッドを使って、定型パターンを減らしつつストリーミングロジックをルーティングに埋め込むこともできます。

カスタムルーティングマッチャー(Custom Route Matchers)

先述のようにSinatraはルーティングマッチャーとして、文字列パターンと正規表現を使うことをビルトインでサポートしています。しかしこれに留まらず、独自のマッチャーを簡単に定義することもできるのです。

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

静的ファイル(Static Files)

静的ファイルは./publicディレクトリから配信されます。 :public_folderオプションを指定することで別の場所を指定することができます。

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

ノート: この静的ファイル用のディレクトリ名はURL中に含まれません。 例えば、./public/css/style.csshttp://example.com/css/style.cssでアクセスできます。

Cache-Controlの設定をヘッダーへ追加するには:static_cache_controlの設定(下記参照)を加えてください。

ビュー / テンプレート(Views / Templates)

各テンプレート言語はそれ自身のレンダリングメソッドを通して展開されます。それらのメソッドは単に文字列を返します。

get '/' do
  erb :index
end

これは、views/index.erbをレンダリングします。

テンプレート名を渡す代わりに、直接そのテンプレートの中身を渡すこともできます。

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

テンプレートのレイアウトは第2引数のハッシュ形式のオプションをもとに表示されます。

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

これは、views/post.erb内に埋め込まれたviews/index.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)。値がシンボルの場合は、使用するテンプレートが指定される。例: erb :index, :layout => !request.xhr?
content_type
テンプレートが生成するContent-Type。デフォルトはテンプレート言語ごとに異なる。
scope
テンプレートをレンダリングするときのスコープ。デフォルトは、アプリケーションのインスタンス。これを変更した場合、インスタンス変数およびヘルパーメソッドが利用できなくなる。
layout_engine
レイアウトをレンダリングするために使用するテンプレートエンジン。レイアウトをサポートしない言語で有用。デフォルトはテンプレートに使われるエンジン。例: set :rdoc, :layout_engine => :erb
layout_options
レイアウトをレンダリングするときだけに使う特別なオプション。例: set :rdoc, :layout_options => { :views => 'views/layouts' }

テンプレートは./viewsディレクトリ下に配置されています。 他のディレクトリを使用する場合の例:

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

テンプレートはシンボルを使用して参照させることを覚えておいて下さい。 サブディレクトリでもこの場合は:'subdir/template'のようにします。 レンダリングメソッドは文字列が渡されると、それをそのまま文字列として出力するので、シンボルを使ってください。

リテラルテンプレート(Literal Templates)

get '/' do
  haml '%div.title Hello World'
end

これはそのテンプレート文字列をレンダリングします。

利用可能なテンプレート言語

いくつかの言語には複数の実装があります。使用する(そしてスレッドセーフにする)実装を指定するには、それを最初にrequireしてください。

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を除く)を呼び出すことができないため、ほぼ全ての場合にlocalsを指定する必要があるでしょう。

Markdown テンプレート

依存 次の何れか: RDiscount, RedCarpet, BlueCloth, kramdown, maruku
ファイル拡張子 .markdown, .mkd and .md
markdown :index, :layout_engine => :erb

Markdownからメソッドを呼び出すことも、localsに変数を渡すこともできません。 それゆえ、他のレンダリングエンジンとの組み合わせで使うのが普通です。

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

ノート: 他のテンプレート内でmarkdownメソッドを呼び出せます。

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

MarkdownからはRubyを呼ぶことができないので、Markdownで書かれたレイアウトを使うことはできません。しかしながら、:layout_engineオプションを渡すことでテンプレートのものとは異なるレンダリングエンジンをレイアウトのために使うことができます。

Textile テンプレート

依存 RedCloth
ファイル拡張子 .textile
textile :index, :layout_engine => :erb

Textileからメソッドを呼び出すことも、localsに変数を渡すこともできません。 それゆえ、他のレンダリングエンジンとの組み合わせで使うのが普通です。

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

ノート: 他のテンプレート内でtextileメソッドを呼び出せます。

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

TexttileからはRubyを呼ぶことができないので、Textileで書かれたレイアウトを使うことはできません。しかしながら、:layout_engineオプションを渡すことでテンプレートのものとは異なるレンダリングエンジンをレイアウトのために使うことができます。

RDoc テンプレート

依存 RDoc
ファイル拡張子 .rdoc
rdoc :README, :layout_engine => :erb

RDocからメソッドを呼び出すことも、localsに変数を渡すこともできません。 それゆえ、他のレンダリングエンジンとの組み合わせで使うのが普通です。

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

ノート: 他のテンプレート内でrdocメソッドを呼び出せます。

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

RDocからはRubyを呼ぶことができないので、RDocで書かれたレイアウトを使うことはできません。しかしながら、:layout_engineオプションを渡すことでテンプレートのものとは異なるレンダリングエンジンをレイアウトのために使うことができます。

AsciiDoc テンプレート

依存 Asciidoctor
ファイル拡張子 .asciidoc, .adoc and .ad
asciidoc :README, :layout_engine => :erb

AsciiDocテンプレートからRubyのメソッドを直接呼び出すことができないため、ほぼ全ての場合にlocalsを指定する必要があるでしょう。

Radius テンプレート

依存 Radius
ファイル拡張子 .radius
radius :index, :locals => { :key => 'value' }

RadiusテンプレートからRubyのメソッドを直接呼び出すことができないため、ほぼ全ての場合にlocalsを指定する必要があるでしょう。

Markaby テンプレート

依存 Markaby
ファイル拡張子 .mab
markaby { h1 "Welcome!" }

インラインテンプレート用にブロックを取ることもできます(例を参照)。

RABL テンプレート

依存 Rabl
ファイル拡張子 .rabl
rabl :index

Slim テンプレート

依存 Slim Lang
ファイル拡張子 .slim
slim :index

Creole テンプレート

依存 Creole
ファイル拡張子 .creole
creole :wiki, :layout_engine => :erb

Creoleからメソッドを呼び出すことも、localsに変数を渡すこともできません。 それゆえ、他のレンダリングエンジンとの組み合わせで使うのが普通です。

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

ノート: 他のテンプレート内でcreoleメソッドを呼び出せます。

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

CreoleからはRubyを呼ぶことができないので、Creoleで書かれたレイアウトを使うことはできません。しかしながら、:layout_engineオプションを渡すことでテンプレートのものとは異なるレンダリングエンジンをレイアウトのために使うことができます。

MediaWiki テンプレート

依存 WikiCloth
ファイル拡張子 .mediawiki および .mw
mediawiki :wiki, :layout_engine => :erb

MediaWikiのテンプレートは直接メソッドから呼び出したり、ローカル変数を通すことはできません。それゆえに、通常は別のレンダリングエンジンと組み合わせて利用します。

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

ノート: 他のテンプレートから部分的にmediawikiメソッドを呼び出すことも可能です。

CoffeeScript テンプレート

依存 CoffeeScript および JavaScriptの起動方法
ファイル拡張子 .coffee
coffee :index

Stylus テンプレート

依存 Stylus および JavaScriptの起動方法
ファイル拡張子 .styl
stylus :index

Stylusテンプレートを使えるようにする前に、まずstylusstylus/tiltを読み込む必要があります。

require 'sinatra'
require 'stylus'
require 'stylus/tilt'

get '/' do
  stylus :example
end

Yajl テンプレート

依存 yajl-ruby
ファイル拡張子 .yajl
yajl :index, :locals => { :key => 'qux' }, :callback => 'present', :variable => 'resource'

テンプレートのソースはRubyの文字列として評価され、その結果のJSON変数は#to_jsonを使って変換されます。

json = { :foo => 'bar' }
json[:baz] = key

:callbackおよび:variableオプションは、レンダリングされたオブジェクトを装飾するために使うことができます。

var resource = {"foo":"bar","baz":"qux"}; present(resource);

WLang テンプレート

依存 wlang
ファイル拡張子 .wlang
wlang :index, :locals => { :key => 'value' }

WLang内でのRubyメソッドの呼び出しは一般的ではないので、ほとんどの場合にlocalsを指定する必要があるでしょう。しかしながら、WLangで書かれたレイアウトはyieldをサポートしています。

テンプレート内での変数へのアクセス

テンプレートはルーティングハンドラと同じコンテキストの中で評価されます。ルーティングハンドラでセットされたインスタンス変数はテンプレート内で直接使うことができます。

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

このやり方は他のテンプレート内で部分テンプレートとして表示する時に典型的に使用されます。

yieldを伴うテンプレートとネストしたレイアウト

レイアウトは通常、yieldを呼ぶ単なるテンプレートに過ぎません。 そのようなテンプレートは、既に説明した:templateオプションを通して使われるか、または次のようなブロックを伴ってレンダリングされます。

erb :post, :layout => false do
  erb :index
end

このコードは、erb :index, :layout => :postとほぼ等価です。

レンダリングメソッドにブロックを渡すスタイルは、ネストしたレイアウトを作るために最も役立ちます。

erb :main_layout, :layout => false do
  erb :admin_layout do
    erb :user
  end
end

これはまた次のより短いコードでも達成できます。

erb :admin_layout, :layout => :main_layout do
  erb :user
end

現在、次のレンダリングメソッドがブロックを取れます: erb, haml, liquid, slim, wlang。 また汎用のrenderメソッドもブロックを取れます。

インラインテンプレート(Inline Templates)

テンプレートはソースファイルの最後で定義することもできます。

require 'sinatra'

get '/' do
  haml :index
end

__END__

ノート: Sinatraをrequireするソースファイル内で定義されたインラインテンプレートは自動的に読み込まれます。他のソースファイル内にインラインテンプレートがある場合にはenable :inline_templatesを明示的に呼んでください。

名前付きテンプレート(Named 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を使います。例えば、Textileテンプレートにttというファイル拡張子を使いたい場合は、以下のようにします。

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についての詳細は、https://github.com/rtomayko/tilt を参照してください。

フィルタ(Filters)

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フィルタでまだ利用できず、その後に生成されることになります。

フィルタにはオプションとしてパターンを渡すことができ、この場合はリクエストのパスがパターンにマッチした場合にのみフィルタが評価されるようになります。

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メソッドを使用してルーティングハンドラやテンプレートで使うヘルパーメソッドを定義できます。

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

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

あるいは、ヘルパーメソッドをモジュール内で個別に定義することもできます。

module FooUtils
  def foo(name) "#{name}foo" end
end

module BarUtils
  def bar(name) "#{name}bar" end
end

helpers FooUtils, BarUtils

その効果は、アプリケーションクラスにモジュールをインクルードするのと同じです。

セッションの使用

セッションはリクエスト間での状態維持のために使用されます。その起動により、ユーザセッションごとに一つのセッションハッシュが与えられます。

enable :sessions

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

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

ノート: enable :sessionsは実際にはすべてのデータをクッキーに保持します。これは必ずしも期待通りのものにならないかもしれません(例えば、大量のデータを保持することでトラフィックが増大するなど)。Rackセッションミドルウェアの利用が可能であり、その場合はenable :sessionsを呼ばずに、選択したミドルウェアを他のミドルウェアのときと同じようにして取り込んでください。

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

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

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

セキュリティ向上のため、クッキー内のセッションデータはセッション秘密鍵(session secret)で署名されます。Sinatraによりランダムな秘密鍵が個別に生成されます。しかし、この秘密鍵はアプリケーションの立ち上げごとに変わってしまうので、すべてのアプリケーションのインスタンスで共有できる秘密鍵をセットしたくなるかもしれません。

set :session_secret, 'super secret'

更に、設定変更をしたい場合は、sessionsの設定においてオプションハッシュを保持することもできます。

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

foo.comのサブドメイン上のアプリ間でセッションを共有化したいときは、代わりにドメインの前に . を付けます。

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

停止(Halting)

フィルタまたはルーティング内で直ちにリクエストを止める場合

halt

この際、ステータスを指定することもできます。

halt 410

body部を指定することも、

halt 'ここにbodyを書く'

ステータスとbody部を指定することも、

halt 401, '立ち去れ!'

ヘッダを付けることもできます。

halt 402, {'Content-Type' => 'text/plain'}, 'リベンジ'

もちろん、テンプレートをhaltに結びつけることも可能です。

halt erb(:error)

パッシング(Passing)

ルーティングはpassを使って次のルーティングに飛ばすことができます。

get '/guess/:who' do
  pass unless params[:who] == 'Frank'
  "見つかっちゃった!"
end

get '/guess/*' do
  "はずれです!"
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の仕様書を参照してください。

ボディ、ステータスコードおよびヘッダの設定

ステータスコードおよびレスポンスボディを、ルーティングブロックの戻り値にセットすることが可能であり、これは推奨されています。しかし、あるケースでは実行フローの任意のタイミングでボディをセットしたくなるかもしれません。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

引数を伴わないbodyheadersstatusなどは、それらの現在の値にアクセスするために使えます。

ストリーミングレスポンス(Streaming Responses)

レスポンスボディの部分を未だ生成している段階で、データを送り出したいということがあります。極端な例では、クライアントがコネクションを閉じるまでデータを送り続けたいことがあります。streamヘルパーを使えば、独自ラッパーを作る必要はありません。

get '/' do
  stream do |out|
    out << "それは伝 -\n"
    sleep 0.5
    out << " (少し待つ) \n"
    sleep 1
    out << "- 説になる!\n"
  end
end

これはストリーミングAPI、Server Sent Eventsの実装を可能にし、WebSocketsの土台に使うことができます。また、一部のコンテンツが遅いリソースに依存しているときに、スループットを上げるために使うこともできます。

ノート: ストリーミングの挙動、特に並行リクエスト(cuncurrent requests)の数は、アプリケーションを提供するのに使われるWebサーバに強く依存します。WEBRickを含むいくつかのサーバは、ストリーミングを全くサポートしません。サーバがストリーミングをサポートしない場合、ボディはstreamに渡されたブロックの実行が終了した後、一度に全部送られることになります。ストリーミングは、Shotgunを使った場合は全く動作しません。

オプション引数がkeep_openにセットされている場合、ストリームオブジェクト上でcloseは呼ばれず、実行フローの任意の遅れたタイミングでユーザがこれを閉じることを可能にします。これはThinやRainbowsのようなイベント型サーバ上でしか機能しません。他のサーバでは依然ストリームは閉じられます。

# ロングポーリング

set :server, :thin
connections = []

get '/subscribe' do
  # サーバイベントにおけるクライアントの関心を登録
  stream(:keep_open) { |out| connections << out }

  # 死んでいるコネクションを排除
  connections.reject!(&:closed?)

  # 肯定応答
  "subscribed"
end

post '/message' do
  connections.each do |out|
    # クライアントへ新規メッセージ到着の通知
    out << params[:message] << "\n"

    # クライアントへの再接続の指示
    out.close
  end

  # 肯定応答
  "message received"
end

ロギング(Logging)

リクエストスコープにおいて、loggerヘルパーはLoggerインスタンスを作り出します。

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

このロガーは、自動でRackハンドラのロギング設定を参照します。ロギングが無効(disabled)にされている場合、このメソッドはダミーオブジェクトを返すので、ルーティングやフィルタにおいて特に心配することはありません。

ノート: ロギングは、Sinatra::Applicationに対してのみデフォルトで有効にされているので、Sinatra::Baseを継承している場合は、ユーザがこれを有効化する必要があります。

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

ロギングミドルウェアが設定されてしまうのを避けるには、logging設定をnilにセットします。しかしこの場合、loggernilを返すことを忘れないように。よくあるユースケースは、オリジナルのロガーをセットしたいときです。Sinatraは、とにかくenv['rack.logger']で見つかるものを使います。

MIMEタイプ(Mime Types)

send_fileか静的ファイルを使う時、SinatraがMIMEタイプを理解できない場合があります。その時は 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ルーティングを、それらがあれば考慮に入れます。

このメソッドにはtoというエイリアスがあります(以下の例を参照)。

ブラウザリダイレクト(Browser Redirect)

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に引数を渡すには、それをクエリーに追加するか、

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

または、セッションを使います。

enable :sessions

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

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

キャッシュ制御(Cache Control)

ヘッダを正しく設定することが、適切なHTTPキャッシングのための基礎となります。

キャッシュ制御ヘッダ(Cache-Control header)は、次のように簡単に設定できます。

get '/' do
  cache_control :public
  "キャッシュしました!"
end

ヒント: キャッシングをbeforeフィルタ内で設定します。

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

expiresヘルパーを対応するヘッダに使っている場合は、キャッシュ制御は自動で設定されます。

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設定(以下を参照)を、キャッシュ制御ヘッダ情報を静的ファイルに追加するために使ってください。

RFC 2616によれば、アプリケーションは、If-MatchまたはIf-None-Matchヘッダが*に設定されている場合には、要求されたリソースが既に存在するか否かに応じて、異なる振る舞いをすべきとなっています。Sinatraは、getのような安全なリクエストおよびputのような冪等なリクエストは既に存在しているものとして仮定し、一方で、他のリソース(例えば、postリクエスト)は新たなリソースとして取り扱われるよう仮定します。この振る舞いは、:new_resourceオプションを渡すことで変更できます。

get '/create' do
  etag '', :new_resource => true
  Article.create
  erb :new_article
end

ここでもWeak ETagを使いたい場合は、:kindオプションを渡してください。

etag '', :new_resource => true, :kind => :weak

ファイルの送信

ファイルを送信するには、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ヘッダ。デフォルトはファイルサイズ。
status
送られるステータスコード。静的ファイルをエラーページとして送るときに便利。 Rackハンドラでサポートされている場合は、Rubyプロセスからのストリーミング以外の手段が使われる。このヘルパーメソッドを使うと、Sinatraは自動で範囲リクエスト(range requests)を扱う。

リクエストオブジェクトへのアクセス

受信するリクエストオブジェクトは、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.bodyの長さ
  request.media_type          # request.bodyのメディアタイプ
  request.host                # "example.com"
  request.get?                # true (他の動詞にも同種メソッドあり)
  request.form_data?          # false
  request["some_param"]       # some_param変数の値。[]はパラメータハッシュのショートカット
  request.referrer            # クライアントのリファラまたは'/'
  request.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 (sslではtrueになる)
  request.forwarded?          # true (リバースプロキシの裏で動いている場合)
  request.env                 # Rackによって渡された生のenvハッシュ
end

script_namepath_infoなどのオプションは次のように利用することもできます。

before { request.path_info = "/" }

get "/" do
  "全てのリクエストはここに来る"
end

request.bodyはIOまたはStringIOのオブジェクトです。

post "/api" do
  request.body.rewind  # 既に読まれているときのため
  data = JSON.parse request.body.read
  "Hello #{data['name']}!"
end

アタッチメント(Attachments)

attachmentヘルパーを使って、レスポンスがブラウザに表示されるのではなく、ディスクに保存されることをブラウザに対し通知することができます。

get '/' do
  attachment
  "保存しました!"
end

ファイル名を渡すこともできます。

get '/' do
  attachment "info.txt"
  "保存しました!"
end

日付と時刻の取り扱い

Sinatraはtime_forヘルパーメソッドを提供しており、それは与えられた値からTimeオブジェクトを生成します。これはまたDateTimeDateおよび類似のクラスを変換できます。

get '/' do
  pass if Time.now > time_for('Dec 23, 2012')
  "まだ時間がある"
end

このメソッドは、expireslast_modifiedといった種類のものの内部で使われています。そのため、アプリケーションにおいて、time_forをオーバーライドすることでそれらのメソッドの挙動を簡単に拡張できます。

helpers do
  def time_for(value)
    case value
    when :yesterday then Time.now - 24*60*60
    when :tomorrow  then Time.now + 24*60*60
    else super
    end
  end
end

get '/' do
  last_modified :yesterday
  expires :tomorrow
  "hello"
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を使うからです。また、テンプレートの場所(および内容)は、developmentモードでの起動でない限りはキャッシュされます。このことは、複雑なメソッド(a really crazy method)を書いた場合は記憶しておく必要があります。

コンフィギュレーション(Configuration)

どの環境でも起動時に1回だけ実行されます。

configure do
  # 1つのオプションをセット
  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

攻撃防御に対する設定

Sinatraは、Rack::Protectionを使って、アプリケーションを多発する日和見的攻撃から守っています。この挙動は簡単に無効化できます(これはアプリケーションを大量の脆弱性攻撃に晒すことになります)。

disable :protection

単一の防御層を外すためには、protectionをオプションハッシュにセットします。

set :protection, :except => :path_traversal

また配列を渡して、複数の防御を無効にすることもできます。

set :protection, :except => [:path_traversal, :session_hijacking]

デフォルトでSinatraは、:sessionsが有効になっている場合、セッションベースの防御だけを設定します。しかし、自身でセッションを設定したい場合があります。その場合は、:sessionオプションを渡すことにより、セッションベースの防御を設定することができます。

use Rack::Session::Pool
set :protection, :session => true

利用可能な設定

absolute_redirects
無効のとき、Sinatraは相対リダイレクトを許容するが、RFC 2616 (HTTP 1.1)は絶対リダイレクトのみを許容するので、これには準拠しなくなる。
アプリケーションが、適切に設定されていないリバースプロキシの裏で走っている場合は有効。ノート: urlヘルパーは、第2引数にfalseを渡さない限り、依然として絶対URLを生成する。
デフォルトは無効。
add_charsets
Mimeタイプ content_typeヘルパーが自動的にキャラクタセット情報をここに追加する。このオプションは書き換えるのではなく、値を追加するようにすること。 settings.add_charsets << "application/foobar"
app_file
メインのアプリケーションファイルのパスであり、プロジェクトのルート、viewsおよびpublicフォルダを見つけるために使われる。
bind
バインドするIPアドレス(デフォルト: `environment`がdevelopmentにセットされているときは、0.0.0.0 または localhost)。ビルトインサーバでのみ使われる。
default_encoding
不明なときに仮定されるエンコーディング(デフォルトは"utf-8")。
dump_errors
ログにおけるエラーの表示。
environment
現在の環境。デフォルトはENV['RACK_ENV']、それが無い場合は"development"
logging
ロガーの使用。
lock
各リクエスト周りのロックの配置で、Rubyプロセスごとにリクエスト処理を並行して走らせるようにする。
アプリケーションがスレッドセーフでなければ有効。デフォルトは無効。
method_override
put/deleteフォームを、それらをサポートしないブラウザで使えるように_methodのおまじないを使えるようにする。
port
待ち受けポート。ビルトインサーバのみで有効。
prefixed_redirects
絶対パスが与えられていないときに、リダイレクトにrequest.script_nameを挿入するか否かの設定。これによりredirect '/foo'は、redirect to('/foo')のように振る舞う。デフォルトは無効。
protection
Web攻撃防御を有効にするか否かの設定。上述の攻撃防御の項を参照。
public_dir
public_folderのエイリアス。以下を参照。
public_folder
publicファイルが提供されるディレクトリのパス。静的ファイルの提供が有効になっている場合にのみ使われる (以下のstatic設定を参照)。設定されていない場合、app_file設定から推定。
reload_templates
リクエスト間でテンプレートを再ロードするか否かの設定。developmentモードでは有効。
root
プロジェクトのルートディレクトリのパス。設定されていない場合、app_file設定から推定。
raise_errors
例外発生の設定(アプリケーションは止まる)。environment"test"に設定されているときはデフォルトは有効。それ以外は無効。
run
有効のとき、SinatraがWebサーバの起動を取り扱う。rackupまたは他の手段を使うときは有効にしないこと。
running
ビルトインサーバが稼働中か?この設定を変更しないこと!
server
ビルトインサーバとして使用するサーバまたはサーバ群の指定。指定順位は優先度を表し、デフォルトはRuby実装に依存。
sessions
Rack::Session::Cookieを使ったクッキーベースのセッションサポートの有効化。詳しくは、'セッションの使用'の項を参照のこと。
show_exceptions
例外発生時にブラウザにスタックトレースを表示する。environment"development"に設定されているときは、デフォルトで有効。それ以外は無効。
また、:after_handlerをセットすることができ、これにより、ブラウザにスタックトレースを表示する前に、アプリケーション固有のエラーハンドリングを起動させられる。
static
Sinatraが静的ファイルの提供を取り扱うかの設定。
その取り扱いができるサーバを使う場合は無効。
無効化でパフォーマンスは改善する
クラッシックスタイルではデフォルトで有効。モジュラースタイルでは無効。
static_cache_control
Sinatraが静的ファイルを提供するときこれをセットして、レスポンスにCache-Controlヘッダを追加するようにする。cache_controlヘルパーを使うこと。デフォルトは無効。
複数の値をセットするときは明示的に配列を使う: set :static_cache_control, [:public, :max_age => 300]
threaded
trueに設定されているときは、Thinにリクエストを処理するためにEventMachine.deferを使うことを通知する。
views
ビューディレクトリのパス。設定されていない場合、app_file設定から推定する。
x_cascade
マッチするルーティングが無い場合に、X-Cascadeヘッダをセットするか否かの設定。デフォルトはtrue

環境設定(Environments)

3種類の既定環境、"development""production"および"test"があります。環境は、RACK_ENV環境変数を通して設定できます。デフォルト値は、"development"です。"development"環境において、すべてのテンプレートは、各リクエスト間で再ロードされ、そして、特別のnot_foundおよびerrorハンドラがブラウザにスタックトレースを表示します。"production"および"test"環境においては、テンプレートはデフォルトでキャッシュされます。

異なる環境を走らせるには、RACK_ENV環境変数を設定します。

RACK_ENV=production ruby my_app.rb

既定メソッド、development?test?およびproduction?を、現在の環境設定を確認するために使えます。

get '/' do
  if settings.development?
    "development!"
  else
    "not development!"
  end
end

エラーハンドリング(Error Handling)

エラーハンドラはルーティングおよびbeforeフィルタと同じコンテキストで実行されます。すなわちこれは、hamlerbhaltといった便利なものが全て使えることを意味します。

未検出(Not Found)

Sinatra::NotFound例外が発生したとき、またはレスポンスのステータスコードが404のときに、not_foundハンドラが発動します。

not_found do
  'ファイルが存在しません'
end

エラー(Error)

errorハンドラはルーティングブロックまたはフィルタ内で例外が発生したときはいつでも発動します。例外オブジェクトはRack変数sinatra.errorから取得できます。

error do
  'エラーが発生しました。 - ' + env['sinatra.error'].name
end

エラーをカスタマイズする場合は、

error MyCustomError do
  'エラーメッセージ...' + env['sinatra.error'].message
end

と書いておいて、下記のように呼び出します。

get '/' do
  raise MyCustomError, '何かがまずかったようです'
end

そうするとこうなります。

エラーメッセージ... 何かがまずかったようです

あるいは、ステータスコードに対応するエラーハンドラを設定することもできます。

error 403 do
  'Access forbidden'
end

get '/secret' do
  403
end

範囲指定もできます。

error 400..510 do
  'Boom'
end

Sinatraを開発環境の下で実行している場合は、特別なnot_foundおよびerrorハンドラが導入され、これは親切なスタックトレースと追加のデバッギング情報をブラウザに表示します。

Rackミドルウェア(Rack Middleware)

SinatraはRuby製Webフレームワークのミニマルな標準的インタフェースであるRack上に構築されています。アプリケーションデベロッパーにとってRackにおける最も興味深い機能は、「ミドルウェア(middleware)」をサポートしていることであり、これは、サーバとアプリケーションとの間に置かれ、HTTPリクエスト/レスポンスを監視および/または操作することで、各種の汎用的機能を提供するコンポーネントです。

Sinatraはトップレベルのuseメソッドを通して、Rackミドルウェアパイプラインの構築を楽にします。

require 'sinatra'
require 'my_custom_middleware'

use Rack::Lint
use MyCustomMiddleware

get '/hello' do
  'Hello World'
end

useの文法は、Rack::BuilderDSLで定義されているそれ(rackupファイルで最もよく使われる)と同じです。例えば useメソッドは複数の引数、そしてブロックも取ることができます。

use Rack::Auth::Basic do |username, password|
  username == 'admin' && password == 'secret'
end

Rackは、ロギング、デバッギング、URLルーティング、認証、セッション管理など、多様な標準的ミドルウェアを共に配布されています。Sinatraはその多くのコンポーネントを自動で使うよう基本設定されているため、通常、それらをuseで明示的に指定する必要はありません。

便利なミドルウェアを以下で見つけられます。

rackrack-contrib、 またはRack wiki

テスト(Testing)

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 "Songbirdを使ってます!", last_response.body
  end
end

ノート: モジュラースタイルでSinatraを使う場合は、上記Sinatra::Applicationをアプリケーションのクラス名に置き換えてください。

Sinatra::Base - ミドルウェア、ライブラリおよびモジュラーアプリ

軽量なアプリケーションであれば、トップレベルでアプリケーションを定義していくことはうまくいきますが、再利用性可能なコンポーネント、例えばRackミドルウェア、RailsのMetal、サーバコンポーネントを含むシンプルなライブラリ、あるいはSinatraの拡張プログラムを構築するような場合、これは無視できない欠点を持つものとなります。トップレベルは、軽量なアプリケーションのスタイルにおける設定(例えば、単一のアプリケーションファイル、./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で利用できるものと全く同じです。ほとんどのトップレベルで記述されたアプリは、以下の2点を修正することでSinatra::Baseコンポーネントに変えることができます。

  • sinatraの代わりにsinatra/baseを読み込む (そうしない場合、SinatraのDSLメソッドの全てがmainの名前空間にインポートされます)
  • ルーティング、エラーハンドラ、フィルタ、オプションをSinatra::Baseのサブクラスに書く

Sinatra::Baseはまっさらです。ビルトインサーバを含む、ほとんどのオプションがデフォルトで無効になっています。利用可能なオプションとその挙動の詳細についてはConfiguring Settings(英語)をご覧下さい。

もしもクラシックスタイルと同じような挙動のアプリケーションをトップレベルで定義させる必要があれば、Sinatra::Applicationをサブクラス化させてください。

require "sinatra/base"

class MyApp < Sinatra::Application
  get "/" do
    'Hello world!'
  end
end

モジュラースタイル vs クラッシックスタイル

一般的認識と違って、クラッシックスタイルを使うことに問題はなにもありません。それがそのアプリケーションに合っているのであれば、モジュラーアプリケーションに移行する必要はありません。

モジュラースタイルを使わずにクラッシックスタイルを使った場合の一番の不利な点は、Rubyプロセスごとにただ一つのSinatraアプリケーションしか持てない点です。複数が必要な場合はモジュラースタイルに移行してください。モジュラースタイルとクラッシックスタイルを混合できないということはありません。

一方のスタイルから他方へ移行する場合、デフォルト設定がわずかに異なる点に注意が必要です。

設定 クラッシック モジュラー モジュラー
app_file sinatraを読み込むファイル Sinatra::Baseをサブクラス化したファイル Sinatra::Applicationをサブクラス化したファイル
run $0 == app_file false false
logging true false true
method_override true false true
inline_templates true false true
static true false true

モジュラーアプリケーションの提供

モジュラーアプリケーションを開始、つまりrun!を使って開始させる二種類のやり方があります。

# my_app.rb
require 'sinatra/base'

class MyApp < Sinatra::Base
  # ... アプリケーションのコードを書く ...

  # Rubyファイルが直接実行されたらサーバを立ち上げる
  run! if app_file == $0
end

として、次のように起動するか、

ruby my_app.rb

または、Rackハンドラを使えるようにするconfig.ruファイルを書いて、

# config.ru (rackupで起動)
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をミドルウェアとして利用し、エンドポイントとしては利用しないとき

モジュラースタイルに移行したという理由だけで、config.ruに移行する必要はなく、config.ruで起動するためにモジュラースタイルを使う必要はありません。

Sinatraのミドルウェアとしての利用

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
  # ミドルウェアはbeforeフィルタの前に実行される
  use LoginScreen

  before do
    unless session['user_name']
      halt "アクセスは拒否されました。<a href='/login'>ログイン</a>してください。"
    end
  end

  get('/') { "Hello #{session['user_name']}." }
end

動的なアプリケーションの生成

新しいアプリケーションを実行時に、定数に割り当てることなく生成したくなる場合があるでしょう。Sinatra.newを使えばそれができます。

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

これは省略できる引数として、それが継承するアプリケーションを取ります。

# config.ru (rackupで起動)
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のextensionをテストするときや、Sinatraを自身のライブラリで利用する場合に役立ちます。

これはまた、Sinatraをミドルウェアとして利用することを極めて簡単にします。

require 'sinatra/base'

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

run RailsProject::Application

スコープとバインディング(Scopes and Binding)

現在のスコープはどのメソッドや変数が利用可能かを決定します。

アプリケーション/クラスのスコープ

全てのSinatraアプリケーションはSinatra::Baseのサブクラスに相当します。 もしトップレベルDSLを利用しているならば(require 'sinatra')このクラスはSinatra::Applicationであり、 そうでなければ、あなたが明示的に作成したサブクラスです。 クラスレベルではgetbeforeのようなメソッドを持っています。 しかしrequestsessionオブジェクトには、全てのリクエストに対する単一のアプリケーションクラスがあるだけなので、アクセスできません。

setによって作られたオプションはクラスレベルのメソッドです。

class MyApp < Sinatra::Base
  # アプリケーションスコープの中だよ!
  set :foo, 42
  foo # => 42

  get '/foo' do
    # もうアプリケーションスコープの中にいないよ!
  end
end

次の場所ではアプリケーションスコープバインディングを持ちます。

  • アプリケーションクラス本体
  • 拡張によって定義されたメソッド
  • helpersに渡されたブロック
  • setの値として使われるProcまたはブロック
  • Sinatra.newに渡されたブロック

このスコープオブジェクト(クラス)は次のように利用できます。

  • configureブロックに渡されたオブジェクト経由(configure { |c| ... })
  • リクエストスコープの中でのsettings

リクエスト/インスタンスのスコープ

やってくるリクエストごとに、あなたのアプリケーションクラスの新しいインスタンスが作成され、全てのハンドラブロックがそのスコープで実行されます。 このスコープの内側からはrequestsessionオブジェクトにアクセスすることができ、erbhamlのようなレンダリングメソッドを呼び出すことができます。 リクエストスコープの内側からは、settingsヘルパーによってアプリケーションスコープにアクセスすることができます。

class MyApp < Sinatra::Base
  # アプリケーションスコープの中だよ!
  get '/define_route/:name' do
    # '/define_route/:name'のためのリクエストスコープ
    @value = 42

    settings.get("/#{params[:name]}") do
      # "/#{params[:name]}"のためのリクエストスコープ
      @value # => nil (not the same request)
    end

    "ルーティングが定義された!"
  end
end

次の場所ではリクエストスコープバインディングを持ちます。

  • get/head/post/put/delete/options/patch/link/unlink ブロック
  • before/after フィルタ
  • helper メソッド
  • テンプレート/ビュー

デリゲートスコープ

デリゲートスコープは、単にクラススコープにメソッドを転送します。 しかしながら、クラスのバインディングを持っていないため、クラススコープと全く同じふるまいをするわけではありません。 委譲すると明示的に示されたメソッドのみが利用可能であり、またクラススコープと変数/状態を共有することはできません(注: 異なったselfを持っています)。 Sinatra::Delegator.delegate :method_nameを呼び出すことによってデリゲートするメソッドを明示的に追加することができます。

次の場所ではデリゲートスコープを持ちます。

  • もしrequire "sinatra"しているならば、トップレベルバインディング
  • Sinatra::Delegator mixinでextendされたオブジェクト

コードをご覧ください: ここでは Sinatra::Delegator mixinmainオブジェクトにextendされています

コマンドライン

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 # rackserver/handlerを指定 (デフォルトはthin)
-x # mutex lockを付ける (デフォルトはoff)

必要環境

次のRubyバージョンが公式にサポートされています。

Ruby 1.8.7
1.8.7は完全にサポートされていますが、特にそれでなければならないという理由がないのであれば、アップグレードまたはJRubyまたはRubiniusへの移行を薦めます。1.8.7のサポートがSinatra 2.0の前に終わることはないでしょう。Ruby 1.8.6はサポート対象外です。
Ruby 1.9.2
1.9.2は完全にサポートされています。1.9.2p0は、Sinatraを起動したときにセグメントフォルトを引き起こすことが分かっているので、使わないでください。公式なサポートは、少なくともSinatra 1.5のリリースまでは続きます。
Ruby 1.9.3
1.9.3は完全にサポート、そして推奨されています。以前のバージョンからの1.9.3への移行は全セッションを無効にする点、覚えておいてください。
Ruby 2.0.0
2.0.0は完全にサポート、そして推奨されています。現在、その公式サポートを終了する計画はありません。
Rubinius
Rubiniusは公式にサポートされています(Rubinius >= 2.x)。 gem install pumaすることが推奨されています。
JRuby
JRubyの最新安定版が公式にサポートされています。JRubyでC拡張を使うことは推奨されていません。 gem install trinidadすることが推奨されています。

開発チームは常に最新となるRubyバージョンに注視しています。

次のRuby実装は公式にはサポートされていませんが、Sinatraが起動すると報告されています。

  • JRubyとRubiniusの古いバージョン
  • Ruby Enterprise Edition
  • MacRuby, Maglev, IronRuby
  • Ruby 1.9.0と1.9.1 (これらの使用はお薦めしません)

公式サポートをしないという意味は、問題がそこだけで起こり、サポートされているプラットフォーム上では起きない場合に、開発チームはそれはこちら側の問題ではないとみなすということです。

開発チームはまた、ruby-head(最新となる2.1.0)に対しCIを実行していますが、それが一貫して動くようになるまで何も保証しません。2.1.0が完全にサポートされればその限りではありません。

Sinatraは、利用するRuby実装がサポートしているオペレーティングシステム上なら動作するはずです。

MacRubyを使う場合は、gem install control_towerしてください。

Sinatraは現在、Cardinal、SmallRuby、BlueRubyまたは1.8.7以前のバージョンのRuby上では動作しません。

最新開発版

Sinatraの最新開発版のコードを使いたい場合は、マスターブランチに対してアプリケーションを走らせて構いません。ある程度安定しています。また、適宜プレリリース版gemをpushしているので、

gem install sinatra --pre

すれば、最新の機能のいくつかを利用できます。

Bundlerを使う場合

最新のSinatraでアプリケーションを動作させたい場合には、Bundlerを使うのがお薦めのやり方です。

まず、Bundlerがなければそれをインストールします。

gem install bundler

そして、プロジェクトのディレクトリで、Gemfileを作ります。

source 'https://rubygems.org'
gem 'sinatra', :github => "sinatra/sinatra"

# 他の依存ライブラリ
gem 'haml'                    # Hamlを使う場合
gem 'activerecord', '~> 3.0'  # ActiveRecord 3.xが必要かもしれません

ノート: Gemfileにアプリケーションの依存ライブラリのすべてを並べる必要があります。しかし、Sinatraが直接依存するもの(RackおよびTile)はBundlerによって自動的に取り込まれ、追加されます。

これで、以下のようにしてアプリケーションを起動することができます。

bundle exec ruby myapp.rb

直接組み込む場合

ローカルにクローンを作って、sinatra/libディレクトリを$LOAD_PATHに追加してアプリケーションを起動します。

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

追ってSinatraのソースを更新する方法。

cd myapp/sinatra
git pull

グローバル環境にインストールする場合

Sinatraのgemを自身でビルドすることもできます。

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

gemをルートとしてインストールする場合は、最後のステップはこうなります。

sudo rake install

バージョニング(Versioning)

Sinatraは、Semantic VersioningにおけるSemVerおよびSemVerTagの両方に準拠しています。

参考文献