LoginSignup
12
13

More than 5 years have passed since last update.

rails console の流れを追う

Posted at

環境
- ruby 2.1.1p76
- Rails 4.1.1

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

参考 rails new の流れを追う

exec_app_rails

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

lib/rails/cli.rb
require 'rails/app_rails_loader'
Rails::AppRailsLoader.exec_app_rails
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

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を確認してみます。
引数が無ければヘルプ表示、各コマンドの省略形の対応を行った後でコマンドを実行するようです。

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/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を用いて、コマンドと同名のファイルを読み込んで実行しています。

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を除く):

$ 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

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で定義されているようです。

$ ack ConsoleMethods

rails/console/app.rb
5:  module ConsoleMethods

rails/console/helpers.rb
2:  module ConsoleMethods

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

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!はここにいたのか!)

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

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
13