まず rails コマンドがどこにあるのか調べよう

$ which rails
/home/plonk/.rvm/gems/ruby-2.1.2/bin/rails

ただの Ruby スクリプトだ。

$ file `which rails`
/home/plonk/.rvm/gems/ruby-2.1.2/bin/rails: Ruby script, ASCII text executable

rails:

#!/usr/bin/env ruby_executable_hooks
#
# This file was generated by RubyGems.
#
# The application 'railties' is installed as part of a gem, and
# this file is here to facilitate running it.
#

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)

最初の行はスクリプトを実行するインタプリタを指定するもので /usr/bin/env を使うことでインタプリタのパスを直接指定せずに環境変数によっ て切り替えるようになっている。

コメントによれば、RubyGems によって生成されたファイルで railties ってい うアプリが関係しているらしい。

第一引数で _VERSION_ の形で railties のバージョンが指定してあれば、それを “>= 0” の代わりに指定して gem 'railties', version を呼び出すというコード。

gem はなにをしているかというと、指定のバージョンの railties が読み込ま れるように $LOAD_PATH を設定するらしい。 http://docs.ruby-lang.org/ja/2.0.0/method/Kernel/i/gem.html

Gem.bin_path の部分は rails コマンドの実行パスを railties ジェムに求め ている様子。 http://apidock.com/ruby/Gem/bin_path/class

実際に実行するとこういうパスが出てくる。

> Gem.bin_path('railties', 'rails')
=> "/home/plonk/.rvm/gems/ruby-2.1.2/gems/railties-4.1.7/bin/rails"

railties っていうのは何かと言うと…

$ gem list railties --details

*** LOCAL GEMS ***

railties (4.1.7)
    Author: David Heinemeier Hansson
    Homepage: http://www.rubyonrails.org
    License: MIT
    Installed at: /home/plonk/.rvm/gems/ruby-2.1.2

    Tools for creating, working with, and running Rails applications.

なるほど、Rails のコマンド類は railties っていうサブパッケージに含まれ ているということかな? (railties は Rails と何のかばん語だろう?)

…ともかく 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"

スクリプトのある場所を基準にして3つ上のディレクトリにある .git ディレク トリを求めている。

plonk@xubuntu-14:~/.rvm/gems/ruby-2.1.2/gems/railties-4.1.7/bin$ ls ../../../.git
ls: ../../../.git にアクセスできません: そのようなファイルやディレクトリはありません

Whoops、存在しない。ともかく、存在すれば ../../lib$: の先頭に追加するということのようだ。

$:$LOAD_PATH と同じ。覚えられないから何度でも確認する。

> $LOAD_PATH == $:
=> true
> $LOAD_PATH.object_id == $:.object_id
=> true

ここまでのところ、自分の環境には関係のないことをしているようだ。そして rails/cli が require されている。cli っていうのは Command Line Interface だろう(どや顔)。

ここにあった。

$ locate rails/cli
/home/plonk/.rvm/gems/ruby-2.1.2/gems/railties-4.1.7/lib/rails/cli.rb

cli.rb:

require 'rails/app_rails_loader'

# If we are inside a Rails application this method performs an exec and thus
# the rest of this script is not run.
Rails::AppRailsLoader.exec_app_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

AppRailsLoader クラスを一行目でロードしているようだ。コメントによれば Rails アプリケーション内から呼び出されたのならば exec するのでこれ以降 は実行されないとある。

exec というのは現在のプロセスを置き換えるシステムコールだから、意味が通 る。http://api.rubyonrails.org/classes/Rails/AppRailsLoader.html

よくわからないが、./bin/rails./script/rails があればそれを実行する ということなのだろう。…はっ、ということは rails と打つとき、そのプロジェ クトの bin ディレクトリにある rails コマンドが実行されていたわけか!

…でも、rails new するときは、これらのファイルはまだ存在しないから exec_app_rails はフォールスルーするはずだ。

rails/ruby_version_check のロードは本質的でなさそうなので、無視する。 次の行は、Ctrl+C が押されて SIGINT が発生したら改行を表示して(きっとエ ラーメッセージとプロンプトがくっつかないようにするためだろう)終了するよ うに設定している。

コマンドライン引数の最初の項目(サブコマンド)が plugin だったら読み捨て て rails/commands/plugin をロードし、それ以外だったら rails/commands/application をロードする。rails new の場合を考えているか ら実行するのは else 節の方のパスのはずだ。

というわけで application.rb に行く。

$ locate rails/commands/application
/home/plonk/.rvm/gems/ruby-2.1.2/gems/railties-4.1.7/lib/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

基本的には AppGenerator クラスをロードして start クラスメソッドにコマン ドライン引数を渡しているようだ。こいつが主な仕事をしているんだろう。

というわけで、 app_generator.rbAppGenerator のスーパークラスが ある app_base.rb を見にいった。だがさっぱりわからないぜ、というわけで あきらめた。