はじめに
- マネーフォワードさんのMF Rails勉強会 ~Rails 1.0のコードを読む~に参加させて頂きました。
- コードリーディングをする上で幾つかお題が出され、読んでみたい分野のチームに参加する
- Railsのroutingやconfig部分を読むチームに参加しました。(が、途中から逸れてwebrickの方に行ってしまいました・・・)
この記事のTopics
- Rails v1.0.0時代はRackが無くて、webrickとlighttpdの2択だった
- 当時はwebrickとlighttpd以外を使うときは、Rails自体に手を入れる必要があった
- DHHは人生何周もしているに違いない(という噂)
Railsのコード準備
git pull https://github.com/rails/rails
cd rails
git checkout v1.0.0
コードリーディングする上でのお題
- Railsの起動周り(routing周りとは初期化)
- の予定だったが、途中から逸れてwebrickの方に行ってしまった
コードリーディング
- Railsのコード単体でなく、Railsから生成したアプリのコードからも読み進めるとわかりやすいのでは?ということで、用意していただいたサンプルアプリのコードと一緒に読み進めました。(MFの担当者の方ありがとうございました!)
- サンプルアプリは「hello_app」
- 以下、本記事のどちらのソースコードを読んでいるかは、
-
hello_app/
:サンプルアプリのコード -
rails/
:rails自体のコード
-
rails起動!(hello_app/script/server)
- 皆さん一番実行されていると思う
rails server
で呼ばれる所
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../config/boot'
require 'commands/server'
-
config/boot
とcommands/server
の処理を行っている - 結果的に、
config/boot
がroutingやconfigの処理だったようだが、いきなり飛ばしてしまった・・・- 最初で脱線ッ・・・・!!!
rails/railties/lib/commands/server.rb
...
(L9)
server = case ARGV.first
when "lighttpd"
ARGV.shift
when "webrick"
ARGV.shift
else
if RUBY_PLATFORM !~ /mswin/ && !silence_stderr { `lighttpd -version` }.blank? && defined?(FCGI)
"lighttpd"
else
"webrick"
end
end
...
- いきなりのパワースポットがくる
- Webサーバはlighttpdかwebrickの2択がべた書きされている
- mswinってwindows server?らしき物もlighttpdで起動
...
(L28)
require "commands/servers/#{server}"
- 上記で選択されたserverへ(ここではwebricに進みました)
rails/railties/lib/commands/servers/webrick.rb
...
(L4)
OPTIONS = {
:port => 3000,
:ip => "0.0.0.0",
:environment => (ENV['RAILS_ENV'] || "development").dup,
:server_root => File.expand_path(RAILS_ROOT + "/public/"),
:server_type => WEBrick::SimpleServer,
:charset => "UTF-8",
:mime_types => WEBrick::HTTPUtils::DefaultMimeTypes
}
- webrickのオプション郡の指定
- 起動メッセージの表示など
...
(L59)
DispatchServlet.dispatch(OPTIONS)
- Servletってなんだ?Java?という雰囲気になった。
- まあまずは読んでみよう。(脱線してたんだけど)
rails/railties/lib/webrick_server.rb
class DispatchServlet < WEBrick::HTTPServlet::AbstractServlet
...
def self.dispatch(options = {})
...
(L61)
server = WEBrick::HTTPServer.new(params)
server.mount('/', DispatchServlet, options)
...
end
...
end
- webrickのオプションをセットして、webrickのインスタンス生成している
- '/' に自身?(DispatchServlet)をマウントしてする
-
DispatchServlet
は何か?
class DispatchServlet < WEBrick::HTTPServlet::AbstractServlet
...
(L72)
def initialize(server, options) #:nodoc:
@server_options = options
@file_handler = WEBrick::HTTPServlet::FileHandler.new(server, options[:server_root])
Dir.chdir(ABSOLUTE_RAILS_ROOT)
super
end
...
end
- webrickのFileHandlerをセットしているっぽい。(この先はwebrickのソースなので読まなかった)
- 結果として、 '/' にwebrickのFileHandlerをマウントしている
- (時間が惜しかったので「そういうもの」として読み進む)
class DispatchServlet < WEBrick::HTTPServlet::AbstractServlet
...
def self.dispatch(options = {})
...
(L67)
require "dispatcher"
server.start
...
end
...
end
-
dispacher
をrequire - その後にサーバをスタートしているっぽい
rails/railties/lib/dispatcher.rb
class Dispatcher
class << self
(L34)
...
def dispatch(cgi = nil, session_options = ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS, output = $stdout)
if cgi ||= new_cgi(output)
request, response = ActionController::CgiRequest.new(cgi, session_options), ActionController::CgiResponse.new(cgi)
prepare_application
ActionController::Routing::Routes.recognize!(request).process(request, response).out(output)
end
...
end
...
end
...
end
- ここが一番重かった
- ActionControllerのrequestクラスとresponseクラスを使っている
-
prepare_application
については先をざっくり読んだがわからなかったので飛ばす
ActionController::Routing::Routes.recognize!(request).process(request, response).out(output)
-
ActionController::Routing::Routes
- おそらくroutes.rb関連のオブジェクトでしょう!という仮説で進む。
rails/actionpack/lib/action_controller/routing.rb(.recognize!(request)
部分)
module ActionController
module Routing #:nodoc:
...
class RouteSet #:nodoc:
...
(L452)
def recognize(request)
...
(L461)
controller = hash['controller']
hash['controller'] = controller.controller_path
request.path_parameters = hash
controller.new
end
alias :recognize! :recognize
...
end
...
end
-
recognize!
のエイリアス - ここも詳細に読み込むことができず、「おそらくcontroller関連のオブジェクトがhashに入っており、それをnewする」と推測
rails/actionpack/lib/action_controller/base.rb(.process(request, response)
部分)
module ActionController #:nodoc:
...
class Base
...
class << self
...
(L361)
def process(request, response, method = :perform_action, *arguments) #:nodoc:
initialize_template_class(response)
assign_shortcuts(request, response)
initialize_current_url
@action_name = params['action'] || 'index'
@variables_added = nil
log_processing if logger
send(method, *arguments)
@response
ensure
close_session
end
...
(L851)
def perform_action
if self.class.action_methods.include?(action_name) || self.class.action_methods.include?('method_missing')
send(action_name)
render unless performed?
elsif template_exists? && template_public?
render
else
raise UnknownAction, "No action responded to #{action_name}", caller
end
end
end
...
end
...
end
- 詳細の読み込むことができなかったが、どこからかactionのmethodを取得してきて実行。
- ざっと見た感じ、
render_text
とかを@responseにセット
rails/actionpack/lib/action_controller/cgi_process.rb(.out(output)
部分)
def out(output = $stdout)
(L163)
convert_content_type!(@headers)
...
end
- headerをセットしている・・・ぽい?
終了
- 終盤に「あれ・・・routingとかconfigとか出てこなくね?」ってなって、一番最初で読む方向を間違ったことに気づく
感想
- これを20代で作り上げるとかすごすぎだろ・・・DHHェ・・・
- 全部を読む時間がなくて、仮説で読み飛ばしてしまったところが多く、あとでじっくり読み直したいと思った。
- webrickにすごい依存した書き方だった
- rackが来るとどう変わるのか興味深い
- これが現在の5系になるまでどのように変わっていくか知りたいと思った
- 1時間で読みましたが、時間もっと欲しいね!
最後に
- 生のRubyコミッタに会えて「ラピュタは本当に存在したんだ!」という気持ちになりました
- とても良い経験をさせていただいたマネーフォワード様に感謝!
- 「v2のソースを読む」とかぜひやってほしい・・・・(最新まで!)
- a_matsudaさんと握手できてよかった!!!
(おまけ)懇親会で聞けたこと
- Railsのバージョンごとの進化について
- v1からv2への大きな変化はREST
- RackはRails v2.2〜2.3あたりで入った
- v2からv3も大きな変更があった(Merbとの結合)
- DHHについて
- 25歳くらいでほぼ一人でrails作り上げたDHHすごい。
- DHHは人生何周もしているに違いない(という噂)
- その他
- Rubyはどんどん早くなっているが、Railsは機能が増えていって重くなっていっている。