2018-02-15

Railsはbin/railsが無いと起動できない

Railsはbin/railsが無いとnew以外のコマンドを実行できません。今回はコードレベルでその理由を追っていこうと思います。railsのバージョンは5.1.4です。

まずbundle exec railsするとexe/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"

exe/railsはさらにlib/rails/cli.rbを呼び出します

require "rails/app_loader"

# If we are inside a Rails application this method performs an exec and thus
# the rest of this script is not run.
Rails::AppLoader.exec_app

require "rails/ruby_version_check"
Signal.trap("INT") { puts; exit(1) }

require "rails/command"

if ARGV.first == "plugin"
  ARGV.shift
  Rails::Command.invoke :plugin, ARGV
else
  Rails::Command.invoke :application, ARGV
end

Rails::AppLoader.exec_appはbin/railsやscript/railsを探し出し、あればそのファイルを引数にexecを呼び出します。

require "pathname"
require "rails/version"

module Rails
  module AppLoader # :nodoc:
    extend self

    RUBY = Gem.ruby
    EXECUTABLES = ["bin/rails", "script/rails"]
# ...
    def exec_app
      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
          elsif exe.end_with?("bin/rails") && contents.include?("This file was generated by Bundler")
# ...

    def find_executable
      EXECUTABLES.find { |exe| File.file?(exe) }
    end
  end
end

bin/railsはこんな感じになってます

#!/usr/bin/env ruby
begin
  load File.expand_path('../spring', __FILE__)
rescue LoadError => e
  raise unless e.message.include?('spring')
end
APP_PATH = File.expand_path('../config/application', __dir__)
require_relative '../config/boot'
require 'rails/commands'

rails/commandsはRails::Command.invokeを呼び出します。このRails::Commandがserverやconsole等を実行する実体となってます。

require "rails/command"

aliases = {
  "g"  => "generate",
  "d"  => "destroy",
  "c"  => "console",
  "s"  => "server",
  "db" => "dbconsole",
  "r"  => "runner",
  "t"  => "test"
}

command = ARGV.shift
command = aliases[command] || command

Rails::Command.invoke command, ARGV

一方でbin/railsやscript/railsが存在しない場合は Rails::Command.invoke の引数が:applicationか:pluginになります。Railsアプリを作成したい場合は Rails::Command.invoke :application, ARGVが呼ばれ、Railsアプリのジェネレータが呼び出されます。new以外のサブコマンドを指定するとhelpを出す部分は Rails::AppBuilder::ARGVScrubber#handle_invalid_command!メソッドになります。

module Rails
  class AppBuilder

    class ARGVScrubber # :nodoc:

      def prepare!
        handle_version_request!(@argv.first)
        handle_invalid_command!(@argv.first, @argv) do
          handle_rails_rc!(@argv.drop(1))
        end
      end

      def self.default_rc_file
        File.expand_path("~/.railsrc")
      end

      private

        def handle_invalid_command!(argument, argv)
          if argument == "new"
            yield
          else
            ["--help"] + argv.drop(1)
          end
        end

ということでbin/railsがないとnewコマンドのみ、あればnewコマンド以外が使えるようになります。

このエントリーをはてなブックマークに追加