- Apr
- 29
- 2015
sendagaya.rb #101 でActionDispatchを読んでみた
101回目を迎えて新たなスタートのsendgaya.rbです。 そろそろ、ちゃんとrailsの内部動作について知らなきゃ感があるので、tkawaさんと相談のもとテーマを決めました。(ほかにもActiveRecordとか、Deviseとか、 Rackとか候補がありました。)
今回の会場は、 南青山の株式会社ランチェスターさんの、ベランダから東京タワーの見えるおしゃれなオフィスをお借りして開催しました。
注意:この記事でActionDispatchの動作の解説ほとんどしてません。
- toggetter まとめ : http://togetter.com/li/814340
- 募集ページ : https://sendagayarb.doorkeeper.jp/events/24213
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 まわりをやろうかなと思います。
Comments