101回目を迎えて新たなスタートのsendgaya.rbです。 そろそろ、ちゃんとrailsの内部動作について知らなきゃ感があるので、tkawaさんと相談のもとテーマを決めました。(ほかにもActiveRecordとか、Deviseとか、 Rackとか候補がありました。)

今回の会場は、 南青山の株式会社ランチェスターさんの、ベランダから東京タワーの見えるおしゃれなオフィスをお借りして開催しました。

注意:この記事でActionDispatchの動作の解説ほとんどしてません。

ActionDispatchについて概要

ActionDispatch については、まずこのスライドをみながらtkawaさんの解説でスタート rackミドルウェア、rackアプリケーションの関係について知れたりします。

https://speakerdeck.com/eiel/actiondispatch-tutenandarou

読むための題材

今回ソースコード・リーディングの題材に使ったデモアプリ

https://github.com/fukajun/demo_app

rails new した後に scaffold post してシンプルな構成で動くようにしたあと index アクションに puts caller を仕込んだ hoge メソッドを呼ぶように 書き換えたRailsアプリケーションを準備しておきました。

def index
  @posts = Posts.all
  hoge
end
def hoge
  puts caller
end

=> http://localhost:3000/posts にアクセス

すると、仕込んでおいた puts caller からずらずらと caller stack ? が表示されます。↓みたいな感じ

https://gist.github.com/fukajun/4518940ff315a49c0166

/Users/fukajun/Dropbox/fukajun/src/github.com/fukajun/demo_app/app/controllers/posts_controller.rb:8:in `index'
/Users/fukajun/.rbenv/versions/2.1.6/lib/ruby/gems/2.1.0/gems/actionpack-4.1.9/lib/action_controller/metal/implicit_render.rb:4:in `send_action'
/Users/fukajun/.rbenv/versions/2.1.6/lib/ruby/gems/2.1.0/gems/actionpack-4.1.9/lib/abstract_controller/base.rb:189:in `process_action'
/Users/fukajun/.rbenv/versions/2.1.6/lib/ruby/gems/2.1.0/gems/actionpack-4.1.9/lib/action_controller/metal/rendering.rb:10:in `process_action'
/Users/fukajun/.rbenv/versions/2.1.6/lib/ruby/gems/2.1.0/gems/actionpack-4.1.9/lib/abstract_controller/callbacks.rb:20:in `block in process_action'

今回は、このcall stack を上のほうから順にたどるというやり方でソースコードを読んでいきました。(呼び出しされた側から呼び出し元へ順にたどっていく)

これだけだと、該当するファイルをエディターで開いて○○行目まで移動しないといけないので、iterm2に 次の設定をすると便利でした。

ワンクリックで該当箇所を開くようにiTerm2を設定

iterm2は、URLやファイルパスを cmd + クリックするとブラウザやエディターで開いてくれる機能があります。なのでこの機能で該当箇所をワンクリックで見れるように設定を変えてみました。(atomを使用してます)

念のため新しく別のProfileとして作ると良いかもしれないです。

  • メニュー => ITerm => Preferences => profile => advancedタブ => semantic history
    • プルダウン: Run command
    • テキストボックス : /usr/local/bin/atom \1:\2

これでこの設定が有効になったセッションで表示された /User/fukajun/a.rb:8 みたいなのをクリックすると a.rb の8行目にカーソルがあたった状態で開かれます。

読んでてどういう値が来て、どういう動作になるかわからないところ

読んでてもいまいち動作が想像できないところがあったりします。 そういうときは、binding.pry を該当箇所に仕込んで再度動かすと 実際の値や動作がつかめて便利でした。

binding.pry
# わからんコード
# わからん値

ただ直接使用中のgemにデバッグ文を書き込んじゃって大丈夫って気分になりますが、gemコマンドには便利な機能があるので、もとに戻すことができます。

1つずつリストアしたい場合

gem pristin gemの名前

全部リストアしたいときに便利なコマンド(--no-extensions ネイティブコードを含むようなものを除外してrestoreできるので早いです。)

gem pristin --no-extensions --all

読んでて面白かったところ

actionpack-4.1.9/lib/action_dispatch/journey/router.rb:59

find_routesメソッドにenv(リクエスト) を渡して、config/routes.rb で 設定したルーティングの中から該当するを検索してます。その結果、該当したルーティングの中からcontrollerとacitonを探してクルクル(each)呼び出しにいっている。ここでリクエストのURLとcontrollerのactionはつながっているんですねぃ。

find_routes(env).each do |match, parameters, route|
  script_name, path_info, set_params = env.values_at('SCRIPT_NAME',
                                                     'PATH_INFO',
                                                     @params_key)

  unless route.path.anchored
    env['SCRIPT_NAME'] = (script_name.to_s + match.to_s).chomp('/')
    matched_path = match.post_match
    env['PATH_INFO']   = matched_path
    env['PATH_INFO']   = "/" + matched_path unless matched_path.start_with? "/"
  end

  env[@params_key] = (set_params || {}).merge parameters

  status, headers, body = route.app.call(env)

  if 'pass' == headers['X-Cascade']
    env['SCRIPT_NAME'] = script_name
    env['PATH_INFO']   = path_info
    env[@params_key]   = set_params
    next
  end

  return [status, headers, body]
end

たくさん積み重なっているrackミドルウェア(callを持つオブジェクト)だけど 処理自体は大きく分けると役割によって次の2種類に分かれていて この辺のコード読んで知りたい処理を探すときに参考になるなと思いました。

1.リクエストをごにょごにょしてイジっているパターン

  def call(env)
      # ごにょごにょ
      status, headers, body = @app.call(env)
      [status, headers, body]
    end

2.レスポンスをごにょごにょしてイジっているパターン

  def call(env)
      status, headers, body = @app.call(env)
      # ごにょごにょ
      [status, headers, body]
    end

懇親会

いつもどおり、21:30 終了なのが盛り上がり 22:00 までやってしまい。そろそろビールを飲める店がなくなりそうなので、強制終了しました。 終了後に行った、中華料理屋さん(なんて名前だったか忘れた)でもrubyとは全然関係ない話で盛り上がり楽しかったです。

まとめ

  • caller使うとどの順序で呼び出されているのかが追いやすくて便利
  • iterm2の設定で直接該当コードをエディターで開けるようにしておくと便利
  • みんなでソースコード読むと各自知ってることを持ち寄って理解できるの良かった
  • action dispatch は、リクエストをroutesの情報を元にcontrollerのactionに繋いでくれる良い奴だった(まだ他の役割わかってない)

ということで、今回はactiondispatchのソースコードリーディングで盛り上がりました。

次回

今回は、ActionDispatch 全体というよりもリクエストがどうやってcontrollerのactionまでやってくるか?というところのみだった気がするのでもう少し掘り下げるか、懇親会で話した ActiveModel まわりをやろうかなと思います。

Recent Entries