Rails Metal の URIマッピング2010/02/26

発端

passenger + Rack 環境にアプリを置こうと思ったのだけど、 いろいろあって、「ActiveRecord があった方が良くね?」という意見が発生。

「それだったら、Rails Metal で良くね?」という結論が、脳内会議で決議された訳だけど、そこで悩んだのが、URIマッピングのルールが良く判らない、という点。

ソースはあんまり見てない(特にroutes関係)なので、あまり信用しない方が良いけど、何かの役に立つかも知れないので、晒しておく。

テンプレート

$ ./script/generate metal foobar

の結果作成される app/metal/foobar.rb のテンプレートは

# Allow the metal piece to run in isolation
require(File.dirname(__FILE__) + "/../../config/environment") unless defined?(Rails)

class Foobar
  def self.call(env)
    if env["PATH_INFO"] =~ /^\/foobar/
      [200, {"Content-Type" => "text/html"}, ["Hello, World!"]]
    else
      [404, {"Content-Type" => "text/html"}, ["Not Found"]]
    end
  end
end

こんな感じになる。 これは(require行とその作用を除けば)、非常に単純なRackアプリケーションそのものなので、 詳細は省略する。

これで、

$ ./script/server &

$ curl http://localhost:3000/foobar
Hello, World!

foobar 以下のURIがこの metal で処理されていることが判る。

routes.rb ?

Rails アプリケーションのURI情報の設定なら、config/routes.rb が正道だ、と思ったが、 どうも routes.rb には metal 関係のキーワードは定義されていない。

ルーティング情報はどこに?

となると、怪しいのは

    if env["PATH_INFO"] =~ /^\/foobar/

この if文だという訳で、regexpを書き換えたりしてみたところ、どうもこいつがルーティング情報らしいことが判明。

# Allow the metal piece to run in isolation
require(File.dirname(__FILE__) + "/../../config/environment") unless defined?(Rails)

class Foobar
  def self.call(env)
    if env["PATH_INFO"] =~ /^\/hogehoge/
      [200, {"Content-Type" => "text/html"}, ["Hello, World!"]]
    else
      [404, {"Content-Type" => "text/html"}, ["Not Found"]]
    end
  end
end

だと

$ curl http://localhost:3000/foobar
(略)
Routing Error
(略)
$ curl http://localhost:3000/hogehoge
Hello, World!

になる。

まとめ

  • metal は routes.rb に書いた規則よりも前に実行される。
  • metal は全てのURIに対して実行される。
  • metal が、404のレスポンスを返した場合は、他の metal(あれば)、routes.rb の規則の適用の順にリクエストが取り回される。
  • 複数の metal が存在する場合の実行順は未定義(ファイルのiノード順っぽい)

ということみたいだ。

演繹できる事実として、同名の metal と controller が居た場合は metal が勝つのと、 controller に落ちるまでには metal が全て実行されるので、極端に metal のクラスが多い場合は要注意かも知れない。

あと、passenger と共に使用する場合は、metal の中で、request.body() とかやってしまった後に、404で次の metal や controller に処理を 回すと大変な事になるような気がする。

(passenger の Rackインターフェイスに入ってくる request.body()はrewindableなオブジェクトではないため)

追記

長々と書いた後で、config/config.ru に rackup用のconfigが書けることに気がついた。

ここなら、 マッピングルールを自在に書ける。

Rails Metal と Controller のセッション共有2010/02/26

Railsノート - セッションまわりを読む (1)

ここが詳しい。

Controller クラスのインスタンスメソッド session で参照できる Hash と同じものが、 Metal クラスでは env['rack.session'] で参照できる。

(env は call の引数)