本家
- Sinatra: README (Japanese)
- Sinatra Recipes(Sinatra Recipes の日本語訳(大感謝))
起動(クラシックスタイル)
ruby myapp.rb [-h] [-x] [-e ENVIRONMENT] [-p PORT] [-o HOST] [-s HANDLER]オプション:
-h # ヘルプ
-p # ポート指定(デフォルトは4567)
-o # ホスト指定(デフォルトは0.0.0.0)
-e # 環境(development/test/production)を指定 (デフォルトはdevelopment)
-s # rackserver/handlerを指定 (デフォルトはthin)
-x # mutex lockを付ける (デフォルトはoff)
Sinatra: README (Japanese)
ちなみに環境指定は環境変数を直接書いてもOK。
RACK_ENV=production ruby my_app.rb
モジュラースタイルでは、 configure ブロック内で以下のような形式でポートなどを指定します。
set :environment, :development set :server, :thin set :bind, 'localhost' set :port, 4567
ルーティング
get '/' do ... end post '/' do ... end
など。書かれた順に判断され、パスと最初に合致したルーティングが処理される。
パスの取得
get '/hello/:name' do
とすると :name
部分のパスの値が params[:name]
で取得出来る。
get '/hello/:name' do |n|
のようにブロックパラメータを付けると、パラメータ n
に params[:name]
が入る。
get '/say/*/to/*' do
のようにワイルドカード(アスタリスク)を使うことも出来る。アスタリスクの値は params[:splat]
(配列)で取得出来る。
get '/download/*.*' do |path, ext|
のようにこれもブロックパラメータで受けることが可能。
get %r{/hello/([\w]+)} do
のように正規表現も使える。正規表現で括弧指定された値は params[:captures]
(配列)で取得出来る。
get %r{/hello/([\w]+)} do |c|
のように、やはりこれもブロックパラメータで受けることが可能。
複数パスをDRYに表記その1
異なるルートで同じ対応をしたい
DRYを実践したい
require 'sinatra' ['/one', '/two', /'three'].each do |route| get rounte do "Triggered #{route} via GET" end post route do "Triggered #{route} via POST" end end
複数パスをDRYに表記その2
get '/foo' do p 'hi' end post '/foo' do p 'hi' end
を Sinatra::MultiRoute で DRY に。
$ gem install sinatra-contrib
して
require 'sinatra' require "sinatra/multi_route" route :get, :post, '/foo' do p 'hi' end
もちろんパスを複数取ることも出来る。
require 'sinatra' require "sinatra/multi_route" get '/foo', '/bar' do # ... end
パラメータ取得
postやputでクライアント側から渡されるパラメータ
form の post で送られるパラメータはやはり params
ハッシュに入れられる。ハッシュキーは input タグの name の値(または値のシンボル)。当然ですよね。
つまり
<html> <head> <title>Params Test</title> </head> <body> <form action="http://localhost:4567/login" method="post"> username: <input type="text" name="username"><br> password: <input type="text" name="password"><br> <input type="submit"> </form> </body> </html>
に対して
require 'sinatra' post '/login' do username = params[:username] password = params[:password] end
で、 post で送られた username と password を取得出来ます。
ただしチェックボックス(<input type="checkbox">)では name に []
を必ず付ける必要があります。 []
を付けると返値が配列になります。しかし付けないと単一の文字列しか返りません。そのため、複数項目をチェックしてもチェック項目の最後の値しか返されません。
参照:はじめてのSinatra - ぱろっと・すたじお の「パラメータの受け渡し」
ストリーミング
動画を送るわけじゃ無いです、文字列を流れ(ストリーム)として送りたい場合です。
サーバ側で処理をしつつ、その進行状況をクライアント側へ送りたい、というのはしばしばあるのではないかと思います。通常であれば結果となる文字列(HTMLなど)をレスポンスとして一括送信するのですが、進行状況を送るのであればちょっとずつちょっとずつ文字列を送ることになります。
このようなことを実現するために、ストリーミングレスポンス (Streaming Responses) という手段を用いることが出来ます。
get '/' do stream do |out| out << "それは伝 -" sleep 0.5 out << " (少し待つ) " sleep 1 out << "- 説になる!" end end
このように stream ヘルパーを用いると、簡単に進行状況を逐一送ることが出来ます。
読み込み終えたらリロード
サーバ側でデータ取得を行うときに、その状況を stream ヘルパーで送信し、データ取得を終えたらリロードさせて本来の表示を行う、というやりかたをどうすれば良いのか悩みました。結局、 JavaScript の location.reload()
を送って読み込ませるのが一番だと分かりました。なお location.reload(true)
のように引数に true を設定しておけば、サーバから新規読み込みしてくれますので true にしておきましょう。
get '/' do stream do |out| out << "読み込み開始<br>" sleep 3 out << "読み込み50%完了<br>" sleep 3 out << "読み込み完了!" sleep 3 out << '<script type="text/javascript">location.reload(true)</script>' end end
クラシックスタイル対モジュラースタイル
Sinatra::Base - ミドルウェア、ライブラリおよびモジュラーアプリ
トップレベル(引用者註:クラシックスタイル?)は、軽量なアプリケーションのスタイルにおける設定(例えば、単一のアプリケーションファイル、./publicおよび./viewsディレクトリ、ロギング、例外詳細ページなど)を仮定しています。
モジュラースタイルの例
require 'sinatra/base' class MyApp < Sinatra::Base # ... アプリケーションのコードを書く ... # Rubyファイルが直接実行されたらサーバを立ち上げる run! if app_file == $0 end
ruby my_app.rb
で Sinatra が起動します。
メリット/デメリット
モジュラースタイル vs クラッシックスタイル - Sinatra: README (Japanese)
一般的認識と違って、クラッシックスタイルを使うことに問題はなにもありません。
モジュラースタイルを使わずにクラッシックスタイルを使った場合の一番の不利な点は、Rubyプロセスごとにただ一つのSinatraアプリケーションしか持てない点です。一方のスタイルから他方へ移行する場合、デフォルト設定がわずかに異なる点に注意が必要です。
set 設定 クラッシック モジュラー
Sinatra::Baseモジュラー
Sinatra::Applicationapp_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
どちらを使う?
Rackとの関連でもこのスタイルのどちらを使うか悩んだりしますが。
config.ruはいつ使うのか? - Sinatra: README (Japanese)
config.ruファイルは、以下の場合に適しています。
- 異なるRackハンドラ(Passenger, Unicorn, Herokuなど)でデプロイしたいとき
- Sinatra::Baseの複数のサブクラスを使いたいとき
- Sinatraをミドルウェアとして利用し、エンドポイントとしては利用しないとき
モジュラースタイルに移行したという理由だけで、config.ruに移行する必要はなく、config.ruで起動するためにモジュラースタイルを使う必要はありません。(引用者強調)
ということで、まずはクラシックスタイルで作ってみて、問題が生じればモジュラースタイルへ移行する、というやり方でよいのではないでしょうか。
configure
どの環境でも起動時に1回だけ実行されます。
環境設定(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
configure の引数を指定すればその環境だけ、指定無しならば全ての環境での設定になります。
configure ブロック内はアプリケーション/クラススコープ。
set/enable/disable
参考:Sinatra: Configuring Settings
アプリケーション/クラススコープにおいて、リクエスト/インスタンススコープで使う任意の設定項目と設定値を登録したり、組込設定項目の設定値を登録して環境設定したりする。
設定値はリクエスト/インスタンススコープにおいて、 settings オブジェクトに生やされた、設定項目と同名のメソッドで取得可能。
アプリケーション/クラススコープで利用可能な機能
- set
- アプリケーション/クラススコープの変数を定義する。組み込みの変数を設定することで基本動作を変更できる他、独自の変数の定義もできる。設定した値は(引用者註:メソッドが作られているので)アプリケーション/クラススコープであればその名称で直接参照でき、リクエスト/インスタンススコープでは settings を使用し参照できる。
set :foo, 'bar' settings.foo #=> "bar"
Proc/lambda での登録も可能。
set :foo, 'bar' set :baz, Proc.new { "Hello " + foo } settings.baz #=>"Hello bar"
複数値同時設定も可能。
set :foo => 'bar', :baz => Proc.new { "Hello " + foo }
enable/disable
enable と disable はそれぞれ true/false の設定のシンタックスシュガー。
すなわち
enable :sessions, :logging disable :dump_errors, :some_custom_option
は
set :sessions, true, :logging, true set :dump_errors, false, :some_custom_option, false
の意味。
Built-in Settings
- :environment - configuration/deployment environment
- :raise_errors - allow exceptions to propagate outside of the app
- :dump_errors - log exception backtraces to STDERR
- :show_exceptions - enable classy error pages
- :sessions - enable/disable cookie based sessions
- :logging - log requests to STDERR
- :method_override - enable/disable the POST _method hack
- :run - enable/disable the built-in web server
- :server - handler used for built-in web server
- :bind - server hostname or IP address
- :port - server port
- :app_file - main application file
- :root - The application’s root directory
- :views - view template directory
- :lock - ensure single request concurrency with a mutex lock
- :public_folder - static files directory
- :static - enable/disable static file routes
初期設定値は sinatra/lib/sinatra/base.rb に記載されています。
module Sinatra class Base set :environment, (ENV['RACK_ENV'] || :development).to_sym set :raise_errors, Proc.new { test? } set :dump_errors, Proc.new { !test? } set :show_exceptions, Proc.new { development? } set :sessions, false set :logging, false set :method_override, false set :run, false # start server via at-exit hook? set :server, %w[HTTP webrick] set :bind, Proc.new { development? ? 'localhost' : '0.0.0.0' } set :port, Integer(ENV['PORT'] && !ENV['PORT'].empty? ? ENV['PORT'] : 4567) set :app_file, nil set :root, Proc.new { app_file && File.expand_path(File.dirname(app_file)) } set :views, Proc.new { root && File.join(root, 'views') } set :lock, false set :public_folder, Proc.new { root && File.join(root, 'public') } set :static, Proc.new { public_folder && File.exist?(public_folder) } end class Application < Base # クラシックスタイルでの初期設定 set :logging, Proc.new { ! test? } set :method_override, true set :run, Proc.new { ! test? } set :session_secret, Proc.new { super() unless development? } set :app_file, nil end end
日本語情報:set, enable, disable による組み込み変数 - Sinatra コンパクトリファレンス - 君の瞳はまるでルビー - Ruby 関連まとめサイト
スコープ
- 「アプリケーション」スコープという「概念」は、「クラス」スコープという「手段によって実現」されている。
- 「リクエスト」スコープという「概念」は、「インスタンス」スコープという「手段によって実現」されている。
Sinatra はリクエスト毎にアプリケーション自身のインスタンスを生成し、そのインスタンスにリクエストを処理させている
本家の README にも載っています。
アプリケーション/クラスのスコープ
クラスレベルではgetやbeforeのようなメソッドを持っています。 しかしrequestやsessionオブジェクトには、全てのリクエストに対する単一のアプリケーションクラスがあるだけなので、アクセスできません。
独自 logging
この方法はかなり非効率なので、もっといい方法がある気がしますが…。
とはいえルーティングブロックでいちいち Logger.new するよりは効率が良いと思います。
まず disable :logging で標準の log を止めます。そして set に Logger.new インスタンスをぶち込みます。
configure do disable :logging _log = Logger.new(STDOUT) _log.progname = 'log' set :_log, _log end
アプリケーション/クラスのスコープで set にぶち込んだインスタンスは、リクエスト/インスタンスのスコープだと settings に生えたメソッドから抜き出せます。
ということで、ルーティングのブロックで使いたい場合には、 settings から logger インスタンスを抜き出してきて、それを使います。
helpers do def log(str) _log = settings._log _log.info(str) end end get '/' do log(str) end
session (つまり Cookie )に情報を全て入れない為に
ネットなどでいちばん良く説明されているのが Rack::Session::Cookie だが、セッション情報の全てを Cookie へ入れてしまうので盗まれた場合の危険性が高い。 Rack::Session::Memcache では、セッション ID のみ cookie に、その他のセッション情報が memcached に保存される(もちろん memcached が動いている必要がある)。 Rack::Session::Pool でも同様に、セッション ID のみ cookie に、その他のセッション情報がインスタンス変数 @pool に保持される(当然ながらアプリを終えれば消える)。
Sinatra で enable :sessions すると使えるセッション情報保持ローカル変数 session は Rack::Session::Cookie に保存される。
Rack::Session::Pool を使う例('15/2/21追記)
use Rack::Session::Pool, path: '/', domain: nil, expire_after: 60 * 10, # Cookieの有効期限を10分に secret: Digest::SHA256.hexdigest(rand.to_s)余談ですが Digest::SHA1.hexdigest(rand.to_s) はもう古いので止めましょうね。
非同期処理とかWorkerプロセスとか
HerokuのcedarスタックでRuby使う時はwebサーバーとしてThinが起動する。
ThinはEventMachineの中で動いてるので、EM::defer等が使える。
Herokuは1プロセス目は無料、2プロセス目を起動させると課金されるが、EventMachineでworkerをWebアプリのプロセス内に同居させればお金がかからなくなる。
ソースを読む
その2
アセットパイプラインをSinatraで
アセットパイプラインとは
JavaScriptやCSSを結合したり圧縮したりする。また、CoffeeScriptやSass、ERBの言語を使ってJavaScriptやCSSを書くこともできる。
- JavaScriptやCSSを結合することによって、ブラウザでのリクエスト回数を削減
- JavaScriptやCSSを圧縮
- SassやCoffeeScript、もしくはERBを使って記述可能
- 「/app/assets/」「/lib/assets/」「/vendor/assets/」のディレクトリ
- 「/app/assets/YYY/XXX」に置いたファイルは、「http://localhost:3000/assets/XXX」でアクセスが可能
- /app/assets/」以下のサブディレクトリは、アクセスする際には無視
ということだそうです。
これを Sinatra で使うには sinatra-assetpack gem を使えば良いとのこと。
- Sinatraサイトでの解説:Sinatra Recipes - Asset Management - Sinatra Assetpack
- gem の GitHub サイト:rstacruz/sinatra-assetpack
- gemの作者サイト:sinatra-assetpack(こちらより README.md のほうが詳しい)
インストール
$ gem install sinatra-assetpack Fetching: jsmin-1.0.1.gem (100%) Fetching: sinatra-assetpack-0.3.3.gem (100%) ################################################## # NOTE FOR UPGRADING FROM PRE-3.0.0 VERSION # ################################################## Please note that sinatra-assetpack `img` helper method no longer sets the image width and height. This default behavior is not desirable with more recent CSS techniques regarding high resolutions images for "retina" displays and "responsive" design CSS using, for example, width:100%. <img src="retina.jpg" width="1200" height="600"> Is now simply this (unless you explicitly set them) : <img src="retina.jpg"> See https://github.com/rstacruz/sinatra-assetpack/pull/121 Successfully installed jsmin-1.0.1 Successfully installed sinatra-assetpack-0.3.3 2 gems installed
なにやら注意書きが入ってますね。画像サイズ指定を生成していたのに Retina 対応のため入れなくなった、ってことでしょうか。
Sinatraアプリケーションサンプル
橋本商会さん
ファイルの配置などが参考になる。そして、ルーティングを別ファイルにしておいて、それらを config.ru で呼び出した Bootstrap.init というメソッド(bootstrap.rb に設定してある)を使って読み込んでいるのも上手い。
Twitter Bootstrapの一例
Twitter Bootstrap を使った一例。なお app.rb は修正する必要あり。
set :public_folder, File.dirname(__FILE__) + '/public' set :haml, :format => :html5
を get '/' do
の外へ出しておく。また get '/room' do
にある
name = request[:name]
を
name = request[:name] || ""
に修正しないと名前未登録時に落ちる。
naoyaさんもの
テンプレ。Sinatra + Slim + Sass + CoffeeScript + jQuery + Underscore.js + Backbone.js。
これに影響を受けたらしいテンプレ。
- Webアプリのモックアップ作業土台を作る その1 - cu39's diary
- Webアプリのモックアップ作業土台を作る その2 Sprockets - cu39's diary
- Webアプリのモックアップ作業土台を作る その3 Sass Source Map - cu39's diary
さらに影響を受けたもの。