Ruby on Railsというフレームワークを使うとrails new Hoge
とかでアプリケーションのひな形ができちゃって、rails server
でサーバーが立ち上げられたりするわけですが、これは一体どうなってるんだというのを追っていけたらなと思います。誰にでもわかるように書きたいです。今回こそはくじけずに書ききりたい。
railsとbin/railsの違い
rails
はシステムにインストールされたrailsコマンドを呼ぶ(/Users/ユーザー名/.rbenv/shims/rails
みたいな)。
bin/rails
はそのプロジェクト下のbin/railsのコマンドを呼ぶ。
bin/rails
Railsプロジェクトを作ると、binというディレクトリの中にrailsというファイルがある。これをエディタで開いてみる。
$ vim bin/rails
中身はこんな感じ。
#!/usr/bin/env ruby APP_PATH = File.expand_path('../../config/application', __FILE__) require_relative '../config/boot' require 'rails/commands'
たった4行だ。
1行目
#!/usr/bin/env ruby
いきなり意味がわからない。
シバン
この記述、「シバン」(shebang)と呼ばれるもので、インタプリタを指定するために書かれるらしい。普通にRubyのスクリプト実行するときはruby rails.rb
みたいに実行するんだけど、今回はrails
というファイルをrubyで解釈してもらいたいわけなので、ファイル内で「Ruby使うよ」っていうのを言ってあげる必要がある。そのため、このshebangが必要になっている。
要は、#!
という2文字でOSに対して「これshebang行だからね」と伝え、/usr/bin/env ruby
でrubyインタプリタを使うと伝えてる。
2行目
APP_PATH = File.expand_path('../../config/application', __FILE__)
APP_PATHに入るのがなにか分からない場合はこの行の後にp APP_PATH
というコードを挿入してbin/rails
を実行すれば見れる。
"/Users/ユーザー名/プロジェクト名/config/application"
このAPP_PATH、どこで使うことになるんだろう...?
3行目
require_relative '../config/boot'
これは普通に、config/boot.rbをrequireしてる。require_relativeを使うと、相対パスを指定してファイルを読み込むことができる。
boot.rb
では、このboot.rbの中身を見てみる。
1行目
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
ENVというのはRubyの環境変数を表すオブジェクト。ハッシュと違う点はキーにも値にも文字列しか指定できないというところ。
何やらプロジェクトのGemfileのパスをENV['BUNDLE_GEMFILE']という環境変数に入れているようだ。||=
という演算子は「無かったら(厳密には「偽か未定義なら」)入れてね」という意味。
2行目
require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
ENV['BUNDLE_GEMFILE']にはGemfileのパスが入っている。「そこにGemfileあったらbundler/setupをrequireしてね」というような意味。
requireはどこを探しに行くのか
この記事がわかりやすい。
requireは引数のファイル名のRubyファイルを読み込んで実行するメソッドです。引数が絶対パスだったときはそのファイルを、そうでない場合はロードパスを優先順位上位から辿って最初に見つかったファイルをロードする。
じゃあ上記のrequire 'bundler/setup'
はどうなってるのか。
rails console
でirbを立ち上げて、以下を実行する。
$LOAD_PATH.each {|path| p path };nil
ぐあーっとパスが表示されるので、bundlerがありそうなところを探す。ちなみに表示する際、返り値として$LOAD_PATHが返ってくるととても見づらいのでここではセミコロンで区切ってnilを返り値にしてる。ぐあーっと表示されるのはほとんどgemのファイルなので、そういうの(kaminariとかjquery-railsとか)は無視してざっと見ると、bundlerのディレクトリが見つかる。
"/Users/ユーザー名/.rbenv/versions/2.1.2/lib/ruby/gems/2.1.0/gems/bundler-1.6.3/lib"
ここに移動する。
cd /Users/ユーザー名/.rbenv/versions/2.1.2/lib/ruby/gems/2.1.0/gems/bundler-1.6.3/lib
で、ls
で見ると、bundlerというディレクトリとbundler.rbというファイルが見つかる。
ls bundler
でbundlerディレクトリの中身を表示。すると、setup.rbというファイルが見つかる。
setup.rb
setup.rbを開くと、430行くらいのコードが書かれている。全部見るのきついので「ああセットアップするんだろーな」という雰囲気だけ感じて、ファイルを閉じる。参照元のboot.rbが、GemfileがあればBundlerをセットアップするというのはわかった。setup.rbももう少し読んだ方がいいんじゃないかと思いつつ逃げるようにbin/railsに戻る。
4行目
4行目はなんだかとても楽しそう。commandsっていう名前からして明らかにこのエントリの本筋に迫る感じだ。
require 'rails/commands'
Railsはrailtiesっていうライブラリにぎっしりと機能が詰まっているというのが記憶のどこかにあったので、それを見てみる。
cd /Users/ユーザー名/プロジェクト名/vendor/bundle/ruby/2.1.0/gems/railties-4.1.4/lib
案の定、railsというディレクトリとrails.rbというファイルがある。今はcommands.rbを探しているので、それを見てみる。
vim rails/commands.rb
こんな感じ。
ARGV << '--help' if ARGV.empty? aliases = { "g" => "generate", "d" => "destroy", "c" => "console", "s" => "server", "db" => "dbconsole", "r" => "runner" } command = ARGV.shift command = aliases[command] || command require 'rails/commands/commands_tasks' Rails::CommandsTasks.new(ARGV).run_command!(command)
おお!実行してる!コマンド実行してる!
rails server
とタイプした時の流れとしては、このcommandという変数に"server"が入り、CommandsTasksインスタンスのメソッドがこれを受け取って処理してくれるみたい。インスタンス初期化の時に渡してるARGVの中身は、コマンド後のオプション。
というわけで、railsコマンドをタイプした時に内部で何が起きてるのかをざっくりと見てみました。ちょっとざっくりしすぎている部分はまた書き足すかもしれません。