LoginSignup
16

More than 5 years have passed since last update.

Padrino Frameworkのルーティングの仕組みが変わります

Posted at

Padrino Frameworkのルーティングの仕組みが変わります

はじめに

タイトルのとおり、Padrino 0.13.0からはコアのルーティング実装が新しいものに替わります。
この記事では、0.13.0から追加された新機能や変更について解説します。

ルーティングの実装については、Padrino Frameworkのルーターを開発した話をご覧ください。

新機能

プリコンパイル機能

新しいルーターでは、目的のルートを高速に探索するために、一度全てのルートを正規表現にコンパイルします。
この機能は、その処理を予めサーバ起動時に行っておくことで、初回リクエスト時のオーバーヘッドを減らすことを目的とするものです。
以下に示すコードをアプリケーション直下、あるいはconfig/apps.rbconfigure_appsブロック内に追加する事で、この機能が有効になります。

set :precompile_routes, true

Sinatraとの互換性の修正

*を含むルートにおいて、*に相当する値を取得できない問題の修正

*に相当する値を取得する方法がありませんでしたが、ブロックパラメータ、もしくはparams[:splat]から値を取得することができます。

get "/a/*" do |b|
  "#{b}, #{params.inspect}"
end

get "/b/*" do
  params.inspect
end

# 0.12.4
get "/a/123" #=> ArgumentError
get "/b/123" #=> {}
# 0.13.0
get "/a/123" #=> "123, {"splat"=>["123"]}"
get "/b/123" #=> {"splat"=>["2"]}

*を含むルートのパスの展開ができない問題の修正

urlurl_forメソッドを使ったパスの展開ができるようになりました。

get :splatter, "/a/*" do
  url(:splatter, splat: "123")
end

get :splatter2, "/b/*/*" do |c, d|
  url(:splatter2, splat: [c, d])
end

# 0.12.4
get "/a/123" #=> "/a?splat=123"
# 0.13.0
get "/a/123" #=> "/a/123"
get "/b/123/456" #=> "/c/123/456"

*を含むルートに対するリクエストにおける、PATH_INFOの末尾スラッシュの有無による挙動の修正

Sinatraの挙動に合わせることにしました。
この機能に依存したルーティングは修正が必要になります。

get("/asdf/*"){ "splat" }

# 0.12.4
get "/asdf/" #=> "splat"
get "/asdf" #=> "splat"

# 0.13.0
get "/asdf/" #=> "splat"
get "/asdf" #=> 404

正規表現ライクなクラスを用いたルートのサポート

なんのこっちゃという感じですが、要はダックタイピングのサポートです。
ただしSinatraのそれとは少し仕様が異なるので注意が必要です。

Sinatraでは、以下に示すようなクラスを用いたルーティングが可能です。

class RegexpLookAlike
  class MatchData
    def captures
      ["this", "is", "a", "test"]
    end
  end

  def match(string)
    ::RegexpLookAlike::MatchData.new if string == "/this/is/a/test/"
  end

  def keys
    ["one", "two", "three", "four"]
  end
end

get RegexpLookAlike.new do
  "this is a test"
end

Padrinoでは、代わりに以下に示すようなクラスが必要になります。

class RegexpLookAlike
  NAMES = ["this", "is", "a", "test"]
  class MatchData
    def captures
      ["this", "is", "a", "test"]
    end

    def names
      NAMES
    end
  end

  def names
    NAMES
  end

  def to_s
    "/this/is/a/test/"
  end

  def match(string)
    ::RegexpLookAlike::MatchData.new if string == "/this/is/a/test/"
  end
end

get RegexpLookAlike do
  "this is a test"
end

見てわかるとおり、MatchData#namesRegexpLookAlike#namesRegexpLookAlike#to_sが新たに必要となります。

プリコンパイル機能に関する説明で触れたように、一度ルートを正規表現にコンパイルする必要があるため、
to_sはそのための正規表現、あるいは文字列を返す必要があります。

mustermannなどと組み合わせて作っていくと良いかもしれません。

pass with ブロックが動かない問題の修正

passing自体は有名ですが、ブロックを渡すことでそれを評価した値をレスポンスボディとして扱うことができる、というような機能もあります。

get "/" do
  pass { "passed" }
  "index"
end

get "/" #=> "passed"

ただし、ブロック付きpassを実行したことで次のルートにマッチし、そのルートが正しくレスポンスを返した場合にはpassに渡されたブロックは無視されます。

get "/" do
  pass { "passed" }
  "index"
end
get "/" do
  "index2"
end

get "/" #=> "index2"

get "/" do
  pass { "passed" }
  "index"
end
get "/" do
  pass
  "index2"
end

get "/" #=> "passed"

ルートブロック内でrequestインスタンスが変更された際のルーティングの挙動の修正

ルートブロック内でrequestインスタンスを変更すると、その変更を考慮したルーティングが行われるようになるというものです。
passと組み合わせて使うことが想定されます。

get "/foo" do
  request.path_info = "/bar"
  pass
end

get "/bar" do
  "bar"
end

get "/foo" #=> "bar"

変更点

(.:format) -> (.:format)?

:providesオプションを使った際に、以前までのPadrinoはパスに対して(.:format)というキャプチャ用パラメータを追加していましたが、
0.13.0からは(.:format)?という指定に変更されます。
これは、Sinatra 2.0のOptional Groupの挙動を模倣したものであり、Padrinoはこの部分において、Sinatra 2.0を先取りしているといえます。
といっても、:providesオプションを介して使っている人はあまり意識する必要はないでしょう。

ブロックパラメータの数に関するサポートの追加

SinatraPadrinoはルートに渡すブロックの仮引数の有無で、実引数を渡すか否かを決定します。
今回のアップデートでは、仮引数が存在していた場合に、仮引数の数と実引数の数が一致しなければPadrinoが用意した例外を発生させるように変更されました。

また、これに伴い0.13.0以前のものとブロックパラメータの扱いに関する挙動が変わっているため、解説します。
以下に示すコードは、0.12.4では拡張子が省略されているケースで正常に動作しているように見えますが、拡張子が渡されるとArgumentErrorが発生します。
0.13.0では、拡張子の有無にかかわらずPadrino::Routing::BlockArityErrorアプリケーションを起動するタイミングで発生するようになりました。

get "/:param1/:param2", provides: :xml do |param1, param2|
  "#{param1}, #{param2}"
end

# 0.12.4
get "/123/456" #=> "123, 456"
get "/123/456.xml" #=> ArgumentError
# 0.13.0
get "/123/456" #=> Padrino::Routing::BlockArityError
get "/123/456.xml" #=> Padrino::Routing::BlockArityError

実際には以下に示すように、仮引数を三つ用意しておくべきです。

get "/:param1/:param2", provides: :xml do |param1, param2, format|
  "#{param1}, #{param2}, #{format}"
end

# 0.12.4
get "/123/456" #=> ArgumentError
get "/123/456.xml" #=> "123, 456, xml"
# 0.13.0
get "/123/456" #=> "123, 456, "
get "/123/456.xml" #=> "123, 456, xml"

おわりに

0.13.0を正式にリリースする前に、何度かbetaリリースが行われることになると思います。(0.13.0.rc1 など)。
是非、Padrinoを利用している方はbetaリリース期間中に自分の環境で新しいバージョンをテストしていただき、不具合等があれば報告していただければと思います。
よろしくお願いします。

この記事は、0.13.0のリリースまで随時更新していきます。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16