環境
- ruby 2.1.1p76
- Rails 4.1.1
railsコマンド
ふとrails new
した時の流れが気になったので・・・
まずはrailsコマンドの在処を調べて、ファイルの中身を確認します。
$ which rails
/Users/usutani/.rvm/gems/ruby-2.1.1@rails411/bin/rails
$ cat `which rails`
ファイル先頭のコメント部を確認すると・・・
#!/usr/bin/env ruby_executable_hooks
#
# このファイルはRubyGemsによって生成されました。
#
# アプリケーションrailtiesはgemの一部としてインストールされ、
# このファイルはその実行を容易にするためここにあります。
#
とのこと。ファイルの続きは・・・
require 'rubygems'
version = ">= 0"
if ARGV.first
str = ARGV.first
str = str.dup.force_encoding("BINARY") if str.respond_to? :force_encoding
if str =~ /\A_(.*)_\z/ and Gem::Version.correct?($1) then
version = $1
ARGV.shift
end
end
gem 'railties', version
load Gem.bin_path('railties', 'rails', version)
if文のブロックでrails _4.1.1_ new
のように第一引数にバージョンが指定された場合に対応する。
指定したバージョンのrailtiesが無ければGem::LoadErrorで終了する。
最後に、バージョンを指定してrailtiesのrailsをロードする。requireでないのは二度目以降も読み込むため。
バージョンを指定しない場合の振る舞いをirbで試してみると・・・
> gem 'railties', ">= 0"
=> true
> load Gem.bin_path('railties', 'rails', ">= 0")
Usage:
rails new APP_PATH [options]
...
> Gem.bin_path('railties', 'rails', ">= 0")
=> "/Users/usutani/.rvm/gems/ruby-2.1.1@rails411/gems/railties-4.1.1/bin/rails"
ロードしているrailtiesのbinディレクトリに存在するrailsファイルの中身を確認します。
#!/usr/bin/env ruby
git_path = File.expand_path('../../../.git', __FILE__)
if File.exist?(git_path)
railties_path = File.expand_path('../../lib', __FILE__)
$:.unshift(railties_path)
end
require "rails/cli"
railtiesディレクトリの上位に .git ディレクトリが存在すれば、自身を優先してロードされるように探索パス$:(組み込み変数)にlibを追加してから、rails/cliをロードしています。
railties/lib
cli.rb
require 'rails/app_rails_loader'
# アプリのrailsコマンドで実行した場合は、残りのスクリプトは実行されません。
Rails::AppRailsLoader.exec_app_rails
今はアプリ外のrailsコマンドを実行しているので、残り(下記)のスクリプトが実行されます。
require 'rails/ruby_version_check'
Signal.trap("INT") { puts; exit(1) }
if ARGV.first == 'plugin'
ARGV.shift
require 'rails/commands/plugin'
else
require 'rails/commands/application'
end
Ctrl + Cが押されたら、プログラムを異常終了する。
rails new
の場合なので、else節に入りrails/commands/applicationがロードされる。
Generators
続けてrails/commands/application.rbを確認します。
require 'rails/generators'
require 'rails/generators/rails/app/app_generator'
module Rails
module Generators
class AppGenerator # :nodoc:
# We want to exit on failure to be kind to other libraries
# This is only when accessing via CLI
def self.exit_on_failure?
true
end
end
end
end
args = Rails::Generators::ARGVScrubber.new(ARGV).prepare!
Rails::Generators::AppGenerator.start args
Generatorは、Rails 3.0からThorをベースにしています。
exit_on_failure?をオーバライドして、失敗時に異常終了させる。
引数を準備して、Generators::AppGeneratorを開始する。
app_generator.rbの外観は次のようになります:
require 'rails/generators/app_base'
module Rails
module ActionMethods # :nodoc:
...
# application builderはapplication generatorの要素を上書きできます。
#
# Gemfile、READMEやJavaScriptファイルの作成のなどのように、あなたは全体の操作を上書きでき
# ます。これらの操作を正確に把握していなくても、別のテンプレートアクションを作成できます。
class AppBuilder
...
module Generators
...
class AppGenerator < AppBase # :nodoc:
...
# このクラスはAppGeneratorが呼ばれる前に引数を準備します。このクラスはバージョンやヘルプ情
# 報を求められれば提供し、(追加の設定オプションに用いる)railsrcファイルも構成します。
#
# このクラスはARGVを設定し正しく変化させるため、AppGeneratorの開始前に、このクラスを呼び
# 出す必要があります。
class ARGVScrubber # :nodoc
...
def prepare!
AppBuilder
このクラスのメソッドで、アプリ内のディレクトリ作成やファイルのコピーなどを行っています。
AppGenerator
AppBuilderとの関係が気になるので調べてみると、get_builder_classメソッドからAppBuilderを呼び出していました。
module Generators
...
class AppGenerator < AppBase # :nodoc:
...
def get_builder_class
defined?(::AppBuilder) ? ::AppBuilder : Rails::AppBuilder
end
このget_builder_classを呼んでいるのは、親クラスAppBaseです。
module Rails
module Generators
class AppBase < Base # :nodoc:
...
protected
...
def builder
@builder ||= begin
builder_class = get_builder_class
builder_class.send(:include, ActionMethods)
builder_class.new(self)
end
end
これらの関係を図示すると次のようになります:
Thor::Groupを派生したAppGeneratorクラスは、定義されたメソッドを順番に実行していきます。
def create_root_files
def create_app_files
def create_bin_files
def create_config_files
def create_boot_file
def create_active_record_files
...
create_root_filesメソッド内のbuildの呼び出しに注目してみると、
def create_root_files
build(:readme)
...
buildはAppBaseで定義されていて、builder=AppBuilderのreadmeを呼んでいます。
def build(meth, *args)
builder.send(meth, *args) if builder.respond_to?(meth)
end
class AppBuilder
...
def readme
copy_file "README.rdoc", "README.rdoc"
end