Rodaのコードリーディングをしました。バージョンは3.8.0です。

以下のconfig.ruで追ってみます。

RodaクラスはRodaPlugins::Base::ClassMethodsをextendsしてクラスメソッドを定義して、#pluginメソッドを呼び出します。

#pluginメソッドは以下のクラスに対して各モジュールをinclude/extendしています。

  • Roda
    • RodaPlugins::InstanceMethodsをinclude
    • RodaPlugins::ClassMethodsをextend
  • RodaPlugins::Base::RodaRequest
    • RodaPlugins::Base::RequestMethodsをinclude
    • RodaPlugins::Base::RequestClassMethodsをextend
  • RodaPlugins::Base::RodaResponse
    • RodaPlugins::Base::ResponseMethodsをinclude
    • RodaPlugins::Base::ResponseClassMethodsをextend

クラスメソッドのrouteはRodaPlugins::ClassMethodsのメソッドで以下のように定義されています。

#build_rack_appはlambdaを使って#callを持つRackアプリを作ります。ミドルウェアが定義されている場合は逆順に元のRackアプリをラップしていきます。app.call(env)の呼び出しで、 Roda.new(env).call(routeのblock)が呼ばれます。

Rodaのコンストラクタや#callはInstanceMethodsに定義されています。RodaRequestはRack::Requestを継承しています。

#callはcatch(:halt)で大域脱出の経路を確保しつつ、blockの中身をRodaRequestのコンテキストで実行します。

RodaRequest#root(RequestMethods#root)は以下のように定義されておりENV[“PATH_INFO”]が”/”と一致するGETリクエストであれば#alwaysが実行されます。

#alwaysは渡されたブロックを実行した結果を#block_resultに渡して、throw :haltによってcallのcatch(:halt)まで抜けます。

RodaResponse#finishは以下のように@statusや@bodyからRackレスポンスを返します。

#block_resultはRodaResponse#writeを呼び出してレスポンスのデータを書き出します。

RodaRequest#redirect(RequestMethods#redirect)は以下のように定義されており、RodaResponse#redirectを呼び出してthrow :haltします。

RodaRequest#onは以下のように定義されており、引数指定されていない場合は#always、引数指定されている場合は#if_matchを呼び出します。

引数指定されているケースを追っていきます。

#if_matchは#match_allでパスがマッチした場合は#block_resultを呼び出して引数のブロックを評価します。

#getはGETリクエストの場合に#_verbメソッドを呼び出します。引数があればTERM(=Object.newのユニークな定数)を引数に追加してから#if_matchを呼び出します。TERMは /hoge/:id/hogeを区別するための番兵オブジェクトでパスの終端を意味しています。

#isは引数が無く評価するパスが無い場合は#always、引数がある場合はTERMを番兵として追加して#if_matchを評価します。

最後に /hoge/:idなどのパラメータがある場合の処理について見ていきます。

config.ruはこんな感じになります。

RodaRequest#getには2つ引数が渡ります。再掲すると以下のような処理になっていて、引数があるのでargsには ['hoge', Integer, TERM]の配列が入り、#if_matchが呼び出されます。

#if_matchはargsを引数に#match_allを呼びだします。#match_allはargsの各々の要素に対して#matchを呼び出します。

‘hoge’はStringクラスなので#_match_stringが呼ばれます

リクエストパス(正確にはネストした先のremaining_path)が”/hoge”から始まっていれば@remaing_pathに残りのパスを入れます。

IntegerはClassクラスなので#_match_classが呼ばれます。Integerの場合はさらに#_match_class_Integerが呼ばれます。

#_match_class_IntegerはIntegerに対する正規表現を生成して、それを引数に#consumeメソッドを呼び出します。

remaining_pathがパターンにマッチした場合はpost_matchした部分をremaining_pathに入れてから、@capturesにマッチしたパラメータをセットします。

この@capturesはmatch_allのif文内のyieldの引数として指定されます。yieldされるブロックは#getで指定されたブロックになります。

こうしてパスから正規表現を使ってパラメータを抽出してブロックの引数としてセットしています。