Railsで学ぶフレームワーク作り - Dispatcher編 その5 Routeの構築部分 freezeメソッドの解析

route.freezeがカオス!前回書いていたたんですが、ようやく何をやってるのか(何故このような処理をしているのか)を理解しました。

freezeメソッドは、以下のようになっています。

      def freeze
        unless frozen?
          write_generation!
          write_recognition!
          prepare_matching!

          parameter_shell
          significant_keys
          defaults
          to_s
        end

        super
      end

write_generationは、dispatch部分には関係ないので次のwrite_recognition!メソッドをみていきましょう。dispatchロジックに深く関係するrecognizeメソッドを触ってますね。

        def write_recognition!
          # Create an if structure to extract the params from a match if it occurs.
          body = "params = parameter_shell.dup\n#{recognition_extraction * "\n"}\nparams"
          body = "if #{recognition_conditions.join(" && ")}\n#{body}\nend"

          # Build the method declaration and compile it
          method_decl = "def recognize(path, env = {})\n#{body}\nend"
          instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})"
          method_decl
        end

何をやってるのかというと、

  • recognition_extractionでparamterを抽出
  • recognition_conditionsでpathのmatchの判定条件を生成
  • recognizeメソッドの文字列を生成
  • recognizeメソッドをinstance_evalを使って今のrouteインスタンスにインストール(今のrouteインスタンスのメソッドの書き換え)

ということをやっています。

インスタンスのメソッド書き換えなんてデバッグ大変そうだし、なんでこんなことやるんだろうと思って不思議に思ってたんですが、method_declをdumpしてみたところ理由がわかりました。

Railsのディスパッチルールは、conf/routes.rbでDSL的に書くのですが、実行時にディスパッチロジックを高速化するために、一旦正規表現をベースにしたrubyコードにこのディスパッチルールを変換しているのでした。

要するにユーザーへのDSLを提供することでディスパッチルールを簡単にかけるようにするのと同時に、実行時のディスパッチロジックの高速化するためにDSLrubyコードにコンパイルしたかったということですね。

いやぁ、しかし複雑ですね。初見だと、何やってるのかさっぱりわかりませんでした。dispatcherは一通りよみおえましたね。

railsが黒魔術を使っているといわれることの理由は少しわかった気がします。