Ruby Freaks Lounge

第41回 Sinatra 1.0の世界にようこそ

この記事を読むのに必要な時間:およそ 6 分

Sinatra.helpers

helpersメソッドと言えば,ブロックを渡し,メソッドを定義する用途のほうがよく知られているかもしれません。そのときの仕組みはずばり,Module#class_evalで,Sinatra::Baseクラスのコンテキストでブロックは評価されます。しかし,拡張モジュールを作成する場合は,主にモジュールの指定に利用します。その場合は,Sinatra::BaseクラスにModule#includeされ,メソッドが追加されます。

このAPIを利用する局面は,Sinatraにグローバルなメソッドを追加するときです。

リスト3 helpersメソッドを用いた拡張モジュールの例: HTMLEscapeHelper

require 'sinatra/base'

module Sinatra
  module HTMLEscapeHelper
    def h(text)
      Rack::Utils.escape_html(text)
    end
  end

  helpers HTMLEscapeHelper
end

リスト4 上記拡張モジュールの利用例

require 'sinatra'
require 'sinatra/htmlescape'

get "/hello" do
  h "1 < 2"     # => "1 < 2"
end

Sinatra.register

registerメソッドもモジュールを指定しますが,helpersメソッドと違うのはobject#extendにより,動作するアプリケーションのインスタンスに特異メソッドとして影響を与えるという点です。つまり,個々のアプリケーションの動作に影響を与える場合に利用します。

具体的には,認証用のアクションを追加する,特定のフィルタ処理を加える等が,利用シーンになるでしょう。また,registeredというメソッドをモジュールに定義した場合,アプリケーションのインスタンスを引数に取ることができますので,アプリケーション動作時の状態や,設定などに変更を加えることも可能です。

リスト5 registerメソッドを用いた拡張モジュールの例: SessionAuth

require 'sinatra/base'

module Sinatra
  module SessionAuth

    module Helpers
      def authorized?
        session[:authorized]
      end

      def authorize!
        redirect '/login' unless authorized?
      end

      def logout!
        session[:authorized] = false
      end
    end

    def self.registered(app)
      app.helpers SessionAuth::Helpers

      app.set :username, 'frank'
      app.set :password, 'changeme'

      app.get '/login' do
        "<form method='POST' action='/login'>" +
        "<input type='text' name='user'>" +
        "<input type='text' name='pass'>" +
        "</form>"
      end

      app.post '/login' do
        if params[:user] == options.username && params[:pass] == options.password
          session[:authorized] = true
          redirect '/'
        else
          session[:authorized] = false
          redirect '/login'
        end
      end
    end
  end

  register SessionAuth
end

リスト6 上記拡張モジュールの利用例

SessionAuth
require 'sinatra'
require 'sinatra/sessionauth'

set :password, 'hoboken'

get '/public' do
  if authorized?
    "Hi. I know you."
  else
    "Hi. We haven't met. <a href='/login'>Login, please.</a>"
  end
end

get '/private' do
  authorize!
  'Thanks for logging in.'
end

参照ドキュメント

公式ドキュメントは英語ですが,上記で引用したサンプルコードに加え,先述した"Modular"スタイルでの拡張モジュールの呼び出し方や,ディレクトリ構成等が解説されています。実際に拡張モジュールを作成する上では,最も重要なドキュメントですので,是非ご一読ください。

現状,日本では拡張モジュール開発の例は少ないようですが,海外では既に開発されている例も少なくありません。その一部はこちらの"Libraries and extensions"に紹介されています。海外でも粒度は様々ですので,日本国内でも今後どんどん,気軽に拡張モジュールが開発されるような状況になれば良いのにな,と個人的には思っています。

まとめ

Sinatraの最初のコミットを今見てみると,トップレベルに,かのシンプルなget,post,put,deleteといったメソッドのみが定義されています。ファイルは現在よりむしろ多く,構造は複雑で,マイクロフレームワークとしてのアイデンティティはそれほど感じられません。そして何より,コミットログに一言残された「this is it」という言葉が印象的です。

こうした発祥から,Sinatraとはつまるところ,⁠極限までにシンプルに,Webアプリケーションは記述できる」という発見だった,と言えるのではないでしょうか。

そして誕生から1.0への流れは,そのシンプルなアイディアを,核はそのままに,現実世界の中でゆるやかに発展させていくものです。

Sinatra::Baseクラスによる"Modular"スタイルのSinatraや,拡張用APIなどの仕組みは,今後Sinatraの可能性を大きく広げていくことになるはずです。もしかすると,始めのシンプルさが失われていき,替わりに強力な機能を手に入れるかもしれません。あるいは,まだ誰も見たことのない,全く新しい何かになるかもしれません。

Sinatraの先には,まだ地図がないと思います。筆者は,そこに強く興味を惹かれます。そこにはWebアプリケーションを記述する上での,自由が広がっています。

もちろん自由には責任がつきまといますが,そのことも含め,次回は筆者自身の業務経験上から,Sinatraを現実の開発でどのように使うかを紹介したいと思います。

著者プロフィール

吉川毅(よしかわつよし)

Tokyu.rb,Asakusa.rb,はたまたJimbocho.rbなど,首都圏のRubyコミュニティにふらふらと出没する,浮気な文系プログラマ。お仕事では主にPHPを使ったtoC向けのサービス開発に従事。最近はソーシャルゲームを開発したりしている。Twitterほか,Web上でのidは"tsuyoshikawa"。実は回文になっている。

http://twitter.com/tsuyoshikawa