こんにちは、キャスレーコンサルティングSI部の反田(そった)です。

今回はRubyの軽量WebアプリDSLといわれるSinatraを紹介します。
Sinatraは説明は以下が詳しいですが
http://www.sinatrarb.com/intro-jp.html
ざっくり説明すると

  • 超簡単にWebアプリが作れて
  • Herokuなどクラウド環境でらくらく公開できる

という特徴があります。
この記事ではSinatraを使ったことがない人向けにSinatraをインストールして実際に簡単なWebアプリを作ってみる実演をします。

インストール

Win環境でまだRuby入っていないならば以下などからインストールして下さい。
http://www.artonx.org/data/asr/

Rubyがすでに使える環境ならばインストールはgemから

gem install sinatra

するだけです。簡単ですね。開発用ツールのsinatra-contribもインストールします。

gem install sinatra-contrib

確認してみましょう。

gem list sinatra

*** LOCAL GEMS ***

sinatra (1.4.3)
sinatra-contrib (1.4.0)

インストールされました。

SinatraをインストールしたらHello Worldしてみます。
以下のようなファイルを作成します。

sinatra_helloworld.rb

require 'sinatra'
get '/' do
    'Hello world!'
end

出来上がったら早速実行してみましょう。

ruby sinatra_helloworld.rb

以下のように出力されました。

== Sinatra/1.4.3 has taken the stage on 4567 for development with backup from Thin
>> Thin web server (v1.5.1 codename Straight Razor)
>> Maximum connections set to 1024
>> Listening on localhost:4567, CTRL+C to stop

Sinatraは引数なしで実行するとlocalhost:4567で起動するようです。localhost:4567にアクセスすると
Hello world!と表示されました。
http://localhost:4567/

スクリーンショット(2013-09-29 12.59.51)

Sinatraではget '/' doの中に処理を書くだけでlocalhost:4567/にアクセスした際の処理が書けてしまいます。
get '/hello' doとすればlocalhost:4567/helloの処理が書けます。設定ファイルなど必要ないので、すごく簡単です。

最後に実行中のSinatra(Rubyスクリプト)を止めてみましょう。起動メッセージの通りコンソールからCtrl+Cと入力すると止まります。止まった後に先ほどのリンクにアクセスすると、エラー画面が表示されるのが確認できます。

^C>> Stopping ...

== Sinatra has ended his set (crowd applauds)

テスト方法

アプリを作る前にごくごく簡単にテスト方法を確認しましょう。
先ほどのsinatra_helloworld.rbと同じフォルダに以下のファイルを作成します。
sinatra_helloworld_test.rb

require './sinatra_helloworld'
require 'test/unit'
require 'rack/test'

class SinatraHelloworldTest < Test::Unit::TestCase
  include Rack::Test::Methods

  def app
    Sinatra::Application
  end

  def test_helloworld
    get '/'
    assert_equal 'Hello world!', last_response.body
  end
end

テストを実行するには以下のようにすればよいです。

ruby sinatra_helloworld_test.rb

結果

Run options:

# Running tests:

Finished tests in 0.041632s, 24.0200 tests/s, 24.0200 assertions/s.
1 tests, 1 assertions, 0 failures, 0 errors, 0 skips

ruby -v: ruby 2.0.0p0 (2013-02-24 revision 39474) [x86_64-darwin10.8.0]

また、assert_equalメソッドの’Hello world!’を’Hello WORLD!’に変更してテストを実行すると失敗することも確認できます。

Run options:

# Running tests:

[1/1] SinatraHelloworldTest#test_helloworld = 0.03 s
  1) Failure:
test_helloworld(SinatraHelloworldTest) [sinatra_helloworld_test.rb:14]:
<"Hello WORLD!"> expected but was
<"Hello world!">.

Finished tests in 0.041901s, 23.8658 tests/s, 23.8658 assertions/s.
1 tests, 1 assertions, 1 failures, 0 errors, 0 skips

ruby -v: ruby 2.0.0p0 (2013-02-24 revision 39474) [x86_64-darwin10.8.0]

test/unitの実行方法はここを参考にしました。Ruby 2.0.0 リファレンスマニュアル > test/unitライブラリ

アプリを作る

さて実際にアプリを作ってみましょう。
何を作るかなのですがこちらの記事を参考にあるブラウザから別のブラウザに画像ファイルを受け渡すアプリを作成したいと思います(rendez-vous)。

以下、簡単に動作を説明します。
画像を送る側をブラウザA、受け取る側をブラウザBとすると2つのパターンで動作します。
1つ目のパターンでは先にファイルの送信が完了しているパターンです。送る側は受け取る側のアクセスがあるまで待ちます。
2つ目のパターンではファイルの受信が先になるパターンです。受け取る側は送る側の処理が終わるまで待ちます。

元記事ではCGIを使っているところをSinatraに変更します。また、drbに関しても説明したかったのですがSinatraの説明にしぼるために省略します。
CGI版からの変更点を見てみましょう。

  • RdVSpaceをアプリケーションスコープでもつ
  • Sinatraでのファイルアップロード方法

実際に作成したスクリプトを示した上で解説します。

require 'sinatra'
require 'sinatra/reloader' if development?

require 'erb'
require 'rinda/tuplespace'

class RdVSpace
	def initialize
		@ts = Rinda::TupleSpace.new(1)
		@expires = 30
	end

	def write(name, type, body)
		_,_,key = @ts.take([:get, name, nil], @expires)
		@ts.write([:post, key, type, body], @expires)
	end

	def take(name)
		key = Object.new
		@ts.write([:get, name, key], @expires)
		_,_, type, body = @ts.take([:post, key, nil, nil], @expires)
		[type, body]
	end
end

set :rdv, RdVSpace.new

get '/' do
	erb RHTML
end

get '/upload' do
	name = params[:name]
	if name != ''
		type, body = settings.rdv.take(name)
		content_type type
		body
	else
		"no name fail."
	end
end

post '/upload' do
	name = params[:name]
	if params[:file]
		type = params[:file][:type]
		file_body = params[:file][:tempfile]
		settings.rdv.write(name, type, file_body)
		"success write"
	else
		redirect "/upload?name=#{name}"
	end
end

RHTML = <<EOS
<html>
  	<title>RdVUp!</title><meta name="viewport" content="width=320" />
  	<body>
    	<form method='post' action='/upload' enctype='multipart/form-data'>
	      <input type='file' name='file'/><br />
	   	key: <input type='textfield' name='name' value=''/><br />
	   	<input type='submit' value='RdV' />
    	</form>
	</body>
</html>
EOS

2行目require 'sinatra/reloader' if development?はsinatra-contribがないと使えません。
開発時だけサーバーを再起動せずにスクリプトの変更が反映されるようになります。便利!

7行目のRdVSpaceクラスではRindaのTupleSpaceを使って待ち合わせ処理を実現しています。元記事からの変更はありません。
とてもおもしろいのですがSinatraはあんまり関係ないです。

26行目 set :rdv, RdVSpace.new
この処理でRdVSpaceをインスタンス化してアプリケーションのスコープで保存しています。
CGI版では別プロセスにずっと存在しているので、Sinatra版でも同様にずっと存在していて欲しいのですが
素直にインスタンス変数として書くとアクセスできません。
これはSinatraにはスコープがありスコープごとにアクセスできるかが変化するためです。
どのリクエストからでもアクセスできるようするためにアプリケーションスコープに保存しています。
くわしくはこちらから

28行目 http://localhost:4567/にアクセスした際に実行されるメソッドです。erbメソッドで送信/受信用の画面を表示します。get,postの処理はリクエストスコープです。

32行目 送信/受信用画面からファイル指定せずにpostするとリダイレクトされて呼ばれます。
35行目のsettings.rdv.take(name)のようにsettingsヘルパーを利用するとリクエストスコープからアプリケーションスコープにアクセスできます。

43行目 送信/受信用画面からpostするとこのメソッドが呼ばれます。
ファイルが指定されているかをチェックします。
ファイルが指定されているかは45行目のように判定します。
アップロードされたファイルは47行目のparams[:file][:tempfile]のように:tempfileで取得します。
48行目では35行目同様アプリケーションスコープにアクセスしています。

55行目 eRuby形式で送信/受信用画面を作成しています。ただのHTMLです。

アプリを公開する

最後に今回作成したアプリをheroku上で公開してみました。
https://www.heroku.com/

herokuに公開するのは初めてでしたがアカウント作成から公開まで半日足らずでできました。すごい!
以下のリンクから動かしてみて下さい。(しばらくは公開しています)
http://intense-bastion-9523.herokuapp.com/

まとめ

今回はSinatraのインストールからテスト、アプリケーションスコープの利用方法、ファイルアップロードの仕方を紹介しました。
秋の夜長のプログラミングのともにSinatraはいかがでしょうか?
それではまた。