Sinatraの実装を読む
対象読者
- Ruby初心者を抜け出したい人
- あまり人のコードとか読まないけどそろそろ読む練習したい人
コードリーディングに慣れてる人だと30分もかからないので,対象読者じゃないかも.
前提知識
- Rackアプリケーションの実装に関する基本的な事項
- Rack解説 - Rackの構造とRack DSL - Qiitaを流し読みしたくらいでも大丈夫
- Sinatraを利用したアプリのつくりかた
- ここでは
get '/' do ... end
で実装するClassicアプリケーションを見ていく
- ここでは
Rack内部の実装については触れず,I/Fだけ知ってる状態という体ですすめる. あくまでSinatraが「どうやって起動するか」「どうやってルーティングを定義してるか」「どうやってリクエストを捌くか」「どうやってDSLを定義しているか」等に着目する.
実装を読む
ここでは2017年1月末時点での最新安定版であるv1.4.7を読む.
sinatra/base.rb
にほとんどの実装が集中しており,頭から読み進めたり秩序なく読んだりは非常にしんどい(そもそもコードリーディングを頭から進めることはないけど).
なので,ここでは以下の3実装に分けて読み進める.
- Rack middlewareの実装
- リクエストを受け取って,どうやって定義されたルーティングに流していくか
- Classic appの実装
- ルーティング実装
- DSLにより定義されたルーティングをどのように保持し,受け取ったリクエストをどのようにdispatchしていくか
以下は読んだコードの順番とちょっとしたメモの記録. メタプロや闇のコードはほとんどない,非常にシンプルな実装なのでそんなにメモることもないという感じ. あとで参照しやすいようにリンクだけ残してある.
注意点として,いま自分がクラスを見ているのか特異クラス(class << self ... end
)を見ているのかを意識しておいたほうがいいかも.
Rack middlewareの実装
- config.ruに
run Sinatra::Application
を記述すれば動くSinatra::Application
はSinatra::Base
のサブクラス https://github.com/sinatra/sinatra/blob/v1.4.7/lib/sinatra/base.rb#L1959Sinatra::Base.call
が起点 https://github.com/sinatra/sinatra/blob/v1.4.7/lib/sinatra/base.rb#L1486-L1488prototype
にはSinatra::Application::Base.new`が入る https://github.com/sinatra/sinatra/blob/v1.4.7/lib/sinatra/base.rb#L1486-L148- 呼ばれるのは
Sinatra::Base#call
およびSinatra::Base#call!
https://github.com/sinatra/sinatra/blob/v1.4.7/lib/sinatra/base.rb#L894-L919 Sinatra::Base#dispatch!
https://github.com/sinatra/sinatra/blob/v1.4.7/lib/sinatra/base.rb#L1081-L109Sinatra::Base#invoke
はブロックの返り値をいい感じに整形するやつ https://github.com/sinatra/sinatra/blob/v1.4.7/lib/sinatra/base.rb#L1066-L1078Sinatra::Base#filter!
はbeforeやafterを呼んでる? https://github.com/sinatra/sinatra/blob/v1.4.7/lib/sinatra/base.rb#L964-L967- この内部で
Sinatra::Base#process_route
が呼ばれてるけどよくわからん
- この内部で
Sinatra::Base#route!
でマッチするルーティング定義を探す https://github.com/sinatra/sinatra/blob/v1.4.7/lib/sinatra/base.rb#L970-L990- パラメータ詰めるのは
Sinatra::Base#process_route
? https://github.com/sinatra/sinatra/blob/v1.4.7/lib/sinatra/base.rb#L1002-L1019 - マッチするやつあったら
:halt
をthrowして抜ける :halt
はSinatra::Base#invoke
がcatchする
- パラメータ詰めるのは
Classic appの実装
sinatra/main
をrequireしてenable :inline_templates
https://github.com/sinatra/sinatra/blob/v1.4.7/lib/sinatra.rb
起動
sinatra/main
でSinatra::Application
つくってrun!
する https://github.com/sinatra/sinatra/blob/v1.4.7/lib/sinatra/main.rb#L25at_exit
はKernel#at_exit
のことSinatra::Base.run!
https://github.com/sinatra/sinatra/blob/v1.4.7/lib/sinatra/base.rb#L1435-L1451Sinatra::Base.detect_rack_handler
https://github.com/sinatra/sinatra/blob/v1.4.7/lib/sinatra/base.rb#L1763-L1774Sinatra::Base.start_server
でhandlerをrunする https://github.com/sinatra/sinatra/blob/v1.4.7/lib/sinatra/base.rb#L1505-L1518
DSL定義
Sinatra::Delegator
をmainにextend https://github.com/sinatra/sinatra/blob/v1.4.7/lib/sinatra/main.rb#L30get
やpost
のようなメソッド群をdefine_method
で定義するdelegate
メソッドを作って呼び出し https://github.com/sinatra/sinatra/blob/v1.4.7/lib/sinatra/base.rb#L1979-L1982- それぞれのメソッドは
Delegator.target
(=Sinatra::Application
)の当該メソッドに移譲するだけ
- それぞれのメソッドは
ルーティング実装
- だいたい
Sinatra::Base.route
になげてるだけ https://github.com/sinatra/sinatra/blob/v1.4.7/lib/sinatra/base.rb#L1369-L1384@routes
にメソッドをキーにした配列が入っていて,そこに追加されるhttps://github.com/sinatra/sinatra/blob/v1.4.7/lib/sinatra/base.rb#L1580-L1588- この
@routes
にいれたルーティングの定義はすべて先に出てきたSinatra::Base#route!
で捌かれる https://github.com/sinatra/sinatra/blob/v1.4.7/lib/sinatra/base.rb#L970-L990base.routes
が@routes
を指すbase = settings
のsettings
はSinatra::Base
自身 https://github.com/sinatra/sinatra/blob/v1.4.7/lib/sinatra/base.rb#L922-L929@routes
はSinatra::Base
のインスタンスではなくSinatra::Base
自身が持ってるので注意
感想
読み進めるに当たっての入り口はわかりやすいのと,そこまで複雑な処理がないということでコードリーディング入門としては悪くない気がする.sinatra/base.rb
が分割されておらずそこそこ巨大なことを除けば.
sinatraの実装が1ファイルに集中してるのなんでなん
— izumin (@izumin5210) January 29, 2017
実装はシンプルなのにそこで複雑そうに見えて初学者のリーディングのハードル無駄に挙げかねない気がする
三時のおやつのお供にどうぞ,くらいの難易度.
(特異クラスまわりが若干ごちゃごちゃなので,メタプロRubyとか読んでると理解しやすいのかも)
- 作者: Peter J. Jones,arton,長尾高弘
- 出版社/メーカー: 翔泳社
- 発売日: 2015/01/09
- メディア: 大型本
- この商品を含むブログ (13件) を見る
- 作者: Paolo Perrotta,角征典
- 出版社/メーカー: オライリージャパン
- 発売日: 2015/10/10
- メディア: 大型本
- この商品を含むブログ (2件) を見る