rails console の流れを追う


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

```rails console```した時のように、アプリ内のrailsコマンドを実行した時の流れを追ってみました。

[参考 rails new の流れを追う](http://qiita.com/usutani/items/a5b814944a8c25d656e5)

# exec_app_rails

```rails new```と同じ処理の流れで、exec_app_railsを呼び出します。

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

```ruby:rails/app_rails_loader.rb
module Rails
  module AppRailsLoader
    RUBY = Gem.ruby
    EXECUTABLES = ['bin/rails', 'script/rails']
...
    def self.exec_app_rails
      original_cwd = Dir.pwd

      loop do
        if exe = find_executable
          contents = File.read(exe)

          if contents =~ /(APP|ENGINE)_PATH/
            exec RUBY, exe, *ARGV
            break # non reachable, hack to be able to stub exec in the test suite
```

railsコマンドを実行する際にカレントディレクトリをアプリ直下にしていますので、bin/railsファイルが見つかります。ここでは見つけたファイルを読み込んで、Rubyのスクリプトとして実行しています。

# bin/rails

```ruby:bin/rails
#!/usr/bin/env ruby
APP_PATH = File.expand_path('../../config/application',  __FILE__)
require_relative '../config/boot'
require 'rails/commands'
```

bin/railsでは、アプリとGemfileの設定を準備してから、フレームワークのcommands.rbをロードしています。

# railties-4.1.1/lib

commands.rbを確認してみます。
引数が無ければヘルプ表示、各コマンドの省略形の対応を行った後でコマンドを実行するようです。

```ruby: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)
```

```ruby:rails/commands/commands_tasks.rb
module Rails
...
  class CommandsTasks # :nodoc:
...
    def run_command!(command)
      command = parse_command(command)
      if COMMAND_WHITELIST.include?(command)
        send(command)
      else
        write_error_message(command)
      end
    end
```

consoleメソッドではrequire_commandを用いて、コマンドと同名のファイルを読み込んで実行しています。

```ruby:rails/commands/commands_tasks.rb
...
    def console
      require_command!("console")
      options = Rails::Console.parse_arguments(argv)

      # RAILS_ENV needs to be set before config/application is required
      ENV['RAILS_ENV'] = options[:environment] if options[:environment]

      # shift ARGV so IRB doesn't freak
      shift_argv!

      require_application_and_environment!
      Rails::Console.start(Rails.application, options)
    end
...
    private
...
      def require_command!(command)
        require "rails/commands/#{command}"
      end
```

# commands

提供されるコマンドは次のようになります（commands_tasks.rbを除く）：

```bash
$ tree rails/commands

rails/commands
├── application.rb
├── commands_tasks.rb
├── console.rb
├── dbconsole.rb
├── destroy.rb
├── generate.rb
├── plugin.rb
├── runner.rb
├── server.rb
└── update.rb

0 directories, 10 files
```

# console

```ruby:rails/commands/console.rb
module Rails
  class Console
    class << self
      def start(*args)
        new(*args).start
      end
...
    def initialize(app, options={})
      @app     = app
      @options = options

      app.sandbox = sandbox?
      app.load_console

      @console = app.config.console || IRB
    end
...
    def start
      ...
      if defined?(console::ExtendCommandBundle)
        console::ExtendCommandBundle.send :include, Rails::ConsoleMethods
      end
      console.start
    end
```

ConsoleMethodsで検索すると・・・app.rbとhelpers.rbで定義されているようです。

```bash
$ ack ConsoleMethods

rails/console/app.rb
5:  module ConsoleMethods

rails/console/helpers.rb
2:  module ConsoleMethods
```

app.rbは次のようになります：

```ruby:rails/console/app.rb
require 'active_support/all'
require 'action_controller'

module Rails
  module ConsoleMethods
    # reference the global "app" instance, created on demand. To recreate the
    # instance, pass a non-false value as the parameter.
    def app(create=false)
      @app_integration_instance = nil if create
      @app_integration_instance ||= new_session do |sess|
        sess.host! "www.example.com"
      end
    end

    # create a new session. If a block is given, the new session will be yielded
    # to the block before being returned.
    def new_session
      app = Rails.application
      session = ActionDispatch::Integration::Session.new(app)
      yield session if block_given?
      session
    end

    # reloads the environment
    def reload!(print=true)
      puts "Reloading..." if print
      ActionDispatch::Reloader.cleanup!
      ActionDispatch::Reloader.prepare!
      true
    end
  end
end
```

シンプルなコードですね。（```reload!```はここにいたのか！）

```ruby:IRB
$ rails c
Loading development environment (Rails 4.1.1)
001 > app.root_url
 => "http://www.example.com/" 
```