🚀

source_locationで始めるRailsソースコードリーディング(URLヘルパーメソッド編)

2022/05/31に公開

3行まとめ

  • Rails初心者がRailsのソースコードリーディングをやってみた
  • URLヘルパーメソッド周りについてソースコードリーディングをした
  • ソースコードリーディングのとっかかりを紹介

はじめに

今回は、URLヘルパーメソッドを題材に、Railsのソースコードを読んでいきたいと思います。

私は去年の11月に転職を機にRailsを触り始めたまだまだひよっこRailsエンジニアです。最近は少し慣れ始め、Railsの内部の実装が少し気になってくるお年頃になってきました。また、Rubyのコードをたくさん読みたいという観点からも、Railsのコードを読むのは勉強になりそうだなと考えていました。
そんな中、RailsのURLヘルパーメソッドに興味を持ちました。最初に見かけた時は、コントローラに対応して勝手にメソッドが生える??なぜ??ととても興味深く思ったものです。
例えば/tasksというパスを表示させたいときは、tasks_pathというメソッドを呼び出せばいいはずです。このことから、Railsのどこかでメソッド名を生成してメソッドを自動で作成してくれるところがあるはず。実際どんな処理をしているのか見てみたい!と思いました。

Railsのソースコードリーディングの始め方

簡単なアプリを作る

scaffoldでRailsアプリを作ります。

rails generate scaffold task content:text

model名は適当でよく、今回はパーフェクトRuby on Railsのチュートリアル例に習いました。
この場合、tasks_pathというメソッドを呼び出すと、対応するpathが帰ってきます。

irb(main):009:0> app.tasks_path
=> "/tasks"

メソッドが定義されているソースコード上の場所を探す

ここで、このtasks_pathというメソッドがどこで定義されているか?というのが分かる便利なメソッドsource_locationというのがあります。
https://docs.ruby-lang.org/ja/latest/method/Method/i/source_location.html

これを用いて、tasks_methodが定義された箇所が分かります。

irb(main):008:0> app.method(:tasks_path).source_location
=> ["/Users/****/projects/sample_app/vendor/bundle/ruby/2.6.0/gems/actionpack-6.0.5/lib/action_dispatch/routing/route_set.rb", 317]

railsのコードで言うとここですね。
https://github.com/rails/rails/blob/main/actionpack/lib/action_dispatch/routing/route_set.rb#L317

          def define_url_helper(mod, name, helper, url_strategy)
            mod.define_method(name) do |*args|
              last = args.last
              options = \
                case last
                when Hash
                  args.pop
                when ActionController::Parameters
                  args.pop.to_h
                end
              helper.call(self, name, args, options, url_strategy)
            end
          end

デバッグして振る舞いを調べる

このメソッドをもう少し調べます。
define_method
https://docs.ruby-lang.org/ja/latest/method/Module/i/define_method.html
が使われているので、ここで何やらメソッドが定義されていそうです。
もう少し詳しく調べるために、コードの中にブレークポイントを設定して実際にデバッグしてみます。

          def define_url_helper(mod, route, name, opts, route_key, url_strategy)
            helper = UrlHelper.create(route, opts, route_key, url_strategy)
            mod.define_method(name) do |*args|
              last = args.last
              options = \
                case last
                when Hash
                  args.pop
                when ActionController::Parameters
                  args.pop.to_h
                end
              binding.pry # ←ここにbinding.pryを入れる
              helper.call self, args, options
            end
          end

このようにRailsのソースコードを修正した上で、再度メソッドを実行します。
railsコンソールで

[1] pry(main)> app.tasks_path

とすると、

[1] pry(#<ActionDispatch::Integration::Session>)> name
=> :tasks_path
[2] pry(#<ActionDispatch::Integration::Session>)> args
=> []
[3] pry(#<ActionDispatch::Integration::Session>)> options
=> nil
[4] pry(#<ActionDispatch::Integration::Session>)> helper.call self, args, options
=> "/tasks"

このことから、このdefine_url_helperという関数では、
tasks_pathというメソッドを定義し、その返り値として、helper.callというメソッドを用いて"/tasks"という文字列が返却されているということが分かりました。
また、引数などには特筆すべきものは渡っていなさそうなことも分かりました。

まとめ

この記事では、source_locationをとっかかりとして、簡単なアプリを作りそれを実際にデバッグしていくことで、RailsのURLヘルパーメソッド周りのソースコードリーディングをしました。
その結果、

  • 生成された文字列からdefine_methodを用いてメソッドを定義している
  • 定義したメソッドの中では生成された文字列を加工して、パスを返却している

ということが分かりました。
簡単なアプリを作りその上でrailsコンソールを開き、source_locationすることでそのメソッドが定義された場所を特定することができます。実際にアプリを動かしてデバッグすることで、その振る舞いを把握することができます。

今後の展望

今回の記事ではRailsのソースコードリーディングのほんの触りを行いましたが、今回扱った範囲だけでも、まだまだ掘り下げがいがあるなと感じています。
例えば

  • そもそものメソッド名、nameで渡ってきたtasks_pathというシンボル名はどこからやってきたか?(おそらくcontroller名だがそこをどのように加工しているか)
  • helper.callではそのメソッド名からどのようにパスを実際に加工して出力しているか?

などといった点があるかと思います。
今後も、少しずつソースコードリーディングをしていきながら、Railsの理解、そしてRubyで書かれたプログラムのソースコードリーディング力を高めていきたいと思っています。

Special Thanks

この記事は職場でのペアプロでRailsのソースコードリーディングをしてみたい!と希望し、そのペアプロで得られた知見を元に執筆したものです。
Rails力を高めたい、フレームワークに詳しくなりたい方には強力なバックアップ体制のある会社だと思いますので興味のある方は是非ご応募ください!
https://bit.ly/334r4bY

Discussion