rails new の流れを追う


**環境**
- ruby 2.1.1p76
- Rails 4.1.1

# railsコマンド

ふと```rails new```した時の流れが気になったので・・・
まずはrailsコマンドの在処を調べて、ファイルの中身を確認します。

```bash
$ which rails
/Users/usutani/.rvm/gems/ruby-2.1.1@rails411/bin/rails
$ cat `which rails`
```

ファイル先頭のコメント部を確認すると・・・

```ruby:path/to/bin/rails
#!/usr/bin/env ruby_executable_hooks
#
# このファイルはRubyGemsによって生成されました。
#
# アプリケーションrailtiesはgemの一部としてインストールされ、
# このファイルはその実行を容易にするためここにあります。
#
```

とのこと。ファイルの続きは・・・

```ruby:path/to/bin/rails
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で試してみると・・・

```ruby: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ファイルの中身を確認します。

```ruby:railties-4.1.1/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

```ruby:lib/rails/cli.rb
require 'rails/app_rails_loader'

# アプリのrailsコマンドで実行した場合は、残りのスクリプトは実行されません。
Rails::AppRailsLoader.exec_app_rails
```

今はアプリ外のrailsコマンドを実行しているので、残り（下記）のスクリプトが実行されます。

```ruby:lib/rails/cli.rb
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を確認します。

```ruby: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をベース](http://guides.rubyonrails.org/generators.html#creating-your-first-generator)にしています。

exit_on_failure?をオーバライドして、失敗時に異常終了させる。
引数を準備して、Generators::AppGeneratorを開始する。

app_generator.rbの外観は次のようになります：

```ruby:rails/generators/rails/app/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を呼び出していました。

```ruby:rails/generators/rails/app/app_generator.rb
  module Generators
...
    class AppGenerator < AppBase # :nodoc:
...
      def get_builder_class
        defined?(::AppBuilder) ? ::AppBuilder : Rails::AppBuilder
      end
```

このget_builder_classを呼んでいるのは、親クラスAppBaseです。

```ruby:rails/generators/app_base.rb
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
```

これらの関係を図示すると次のようになります：

![generators.png](https://qiita-image-store.s3.amazonaws.com/0/5179/c28d8705-09dd-f43c-d099-e37e7546f3b5.png "generators.png")

Thor::Groupを派生したAppGeneratorクラスは、定義されたメソッドを順番に実行していきます。

```ruby:rails/generators/rails/app/app_generator.rb
      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の呼び出しに注目してみると、

```ruby:rails/generators/rails/app/app_generator.rb
      def create_root_files
        build(:readme)
        ...
```

buildはAppBaseで定義されていて、builder＝AppBuilderのreadmeを呼んでいます。

```ruby:rails/generators/app_base.rb
      def build(meth, *args)
        builder.send(meth, *args) if builder.respond_to?(meth)
      end
```

```ruby:rails/generators/rails/app/app_generator.rb
  class AppBuilder
...
    def readme
      copy_file "README.rdoc", "README.rdoc"
    end
```