edge
詳細はrubyonrails.orgで: もっとRuby on Rails

Railsの初期化プロセス

このガイドでは、Railsの初期化プロセスの内部について説明します。 これは非常に詳細なガイドであり、上級のRails開発者におすすめです。

このガイドを読み終えると、以下のことがわかります。

このガイドでは、デフォルトのRailsアプリケーションを起動するために必要な すべてのメソッド呼び出しを詳細に説明します。このガイドでは、bin/rails server を実行した場合に何が起こるかに焦点を当てます。

注意:このガイドのパスは、それ以外が指定されていない限り、RailsまたはRailsアプリケーションに対して相対的です。

ヒント:Railsのソースコードをブラウズしながら進む場合は、GitHub内でファイルファインダーを開くためにtキーのバインディングを使用することをおすすめします。

1 起動!

アプリを起動して初期化するためには、通常bin/rails consoleまたはbin/rails serverを実行します。

1.1 bin/rails

このファイルは次のようになっています:

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

APP_PATH定数は後でrails/commandsで使用されます。ここで参照されているconfig/bootファイルは、アプリケーションのconfig/boot.rbファイルであり、Bundlerの読み込みと設定を行う責任があります。

1.2 config/boot.rb

config/boot.rbには次の内容が含まれています:

ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)

require "bundler/setup" # Set up gems listed in the Gemfile.

標準のRailsアプリケーションでは、Gemfileにアプリケーションのすべての依存関係が宣言されています。config/boot.rbENV['BUNDLE_GEMFILE']をこのファイルの場所に設定します。Gemfileが存在する場合、bundler/setupが必要とされます。このrequireは、BundlerがGemfileの依存関係のためのロードパスを設定するために使用されます。

1.3 rails/commands.rb

config/boot.rbが完了した後、次に必要なファイルはrails/commandsです。このファイルはエイリアスの展開を支援します。現在の場合、ARGV配列には単にserverが含まれています:

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

もしserverの代わりにsを使用した場合、Railsはここで定義されたエイリアスを使用して一致するコマンドを見つけます。

1.4 rails/command.rb

Railsコマンドを入力すると、invokeは指定された名前空間のコマンドを検索し、見つかった場合はコマンドを実行しようとします。

もしRailsがコマンドを認識しない場合、同じ名前のタスクを実行するためにRakeに制御を渡します。

表示されるように、Rails::Commandnamespaceが空の場合に自動的にヘルプ出力を表示します。

module Rails
  module Command
    class << self
      def invoke(full_namespace, args = [], **config)
        namespace = full_namespace = full_namespace.to_s

        if char = namespace =~ /:(\w+)$/
          command_name, namespace = $1, namespace.slice(0, char)
        else
          command_name = namespace
        end

        command_name, namespace = "help", "help" if command_name.blank? || HELP_MAPPINGS.include?(command_name)
        command_name, namespace = "version", "version" if %w( -v --version ).include?(command_name)

        command = find_by_namespace(namespace, command_name)
        if command && command.all_commands[command_name]
          command.perform(command_name, args, config)
        else
          find_by_namespace("rake").perform(full_namespace, args, config)
        end
      end
    end
  end
end

serverコマンドを使用する場合、Railsはさらに次のコードを実行します:

module Rails
  module Command
    class ServerCommand < Base # :nodoc:
      def perform
        extract_environment_option_from_argument
        set_application_directory!
        prepare_restart

        Rails::Server.new(server_options).tap do |server|
          # Require application after server sets environment to propagate
          # the --environment option.
          require APP_PATH
          Dir.chdir(Rails.application.root)

          if server.serveable?
            print_boot_information(server.server, server.served_url)
            after_stop_callback = -> { say "Exiting" unless options[:daemon] }
            server.start(after_stop_callback)
          else
            say rack_server_suggestion(using)
          end
        end
      end
    end
  end
end

このファイルはRailsのルートディレクトリに変更します(APP_PATHconfig/application.rbを指し示す2つ上のディレクトリのパスです)、ただしconfig.ruファイルが見つからない場合のみです。その後、Rails::Serverクラスを起動します。

1.5 actionpack/lib/action_dispatch.rb

Action DispatchはRailsフレームワークのルーティングコンポーネントです。 ルーティング、セッション、共通ミドルウェアなどの機能を追加します。

1.6 rails/commands/server/server_command.rb

Rails::Serverクラスは、Rack::Serverを継承してこのファイルで定義されています。Rails::Server.newが呼び出されると、rails/commands/server/server_command.rbinitializeメソッドが呼び出されます:

module Rails
  class Server < ::Rack::Server
    def initialize(options = nil)
      @default_options = options || {}
      super(@default_options)
      set_environment
    end
  end
end

まず、superが呼び出され、Rack::Serverinitializeメソッドが呼び出されます。

1.7 Rack: lib/rack/server.rb

Rack::Serverは、すべてのRackベースのアプリケーションに共通のサーバーインターフェースを提供する責任を持っており、Railsもその一部です。

Rack::Serverinitializeメソッドは、単純にいくつかの変数を設定します。

module Rack
  class Server
    def initialize(options = nil)
      @ignore_options = []

      if options
        @use_default_options = false
        @options = options
        @app = options[:app] if options[:app]
      else
        argv = defined?(SPEC_ARGV) ? SPEC_ARGV : ARGV
        @use_default_options = true
        @options = parse_options(argv)
      end
    end
  end
end

この場合、Rails::Command::ServerCommand#server_optionsの戻り値がoptionsに割り当てられます。 if文内の行が評価されると、いくつかのインスタンス変数が設定されます。

Rails::Command::ServerCommandserver_optionsメソッドは次のように定義されています。

module Rails
  module Command
    class ServerCommand
      no_commands do
        def server_options
          {
            user_supplied_options: user_supplied_options,
            server:                using,
            log_stdout:            log_to_stdout?,
            Port:                  port,
            Host:                  host,
            DoNotReverseLookup:    true,
            config:                options[:config],
            environment:           environment,
            daemonize:             options[:daemon],
            pid:                   pid,
            caching:               options[:dev_caching],
            restart_cmd:           restart_command,
            early_hints:           early_hints
          }
        end
      end
    end
  end
end

その値はインスタンス変数@optionsに割り当てられます。

Rack::Serversuperが終了した後、rails/commands/server/server_command.rbに戻ります。この時点で、set_environmentRails::Serverオブジェクトのコンテキスト内で呼び出されます。

module Rails
  module Server
    def set_environment
      ENV["RAILS_ENV"] ||= options[:environment]
    end
  end
end

initializeが終了した後、サーバーコマンドに戻り、APP_PATH(前に設定された)が必要とされます。

1.8 config/application

require APP_PATHが実行されると、config/application.rbがロードされます(APP_PATHbin/railsで定義されていることを思い出してください)。このファイルはアプリケーションに存在し、必要に応じて変更することができます。

1.9 Rails::Server#start

config/applicationがロードされた後、server.startが呼び出されます。このメソッドは次のように定義されています。

module Rails
  class Server < ::Rack::Server
    def start(after_stop_callback = nil)
      trap(:INT) { exit }
      create_tmp_directories
      setup_dev_caching
      log_to_stdout if options[:log_stdout]

      super()
      # ...
    end

    private
      def setup_dev_caching
        if options[:environment] == "development"
          Rails::DevCaching.enable_by_argument(options[:caching])
        end
      end

      def create_tmp_directories
        %w(cache pids sockets).each do |dir_to_make|
          FileUtils.mkdir_p(File.join(Rails.root, "tmp", dir_to_make))
        end
      end

      def log_to_stdout
        wrapped_app # touch the app so the logger is set up

        console = ActiveSupport::Logger.new(STDOUT)
        console.formatter = Rails.logger.formatter
        console.level = Rails.logger.level

        unless ActiveSupport::Logger.logger_outputs_to?(Rails.logger, STDOUT)
          Rails.logger.extend(ActiveSupport::Logger.broadcast(console))
        end
      end
  end
end

このメソッドはINTシグナルのトラップを作成し、サーバーをCTRL-Cで終了するとプロセスが終了します。 ここで、tmp/cachetmp/pidstmp/socketsディレクトリを作成します。また、bin/rails server--dev-cachingオプションで呼び出された場合、開発環境でキャッシュを有効にします。最後に、Rackアプリを作成し、ActiveSupport::Loggerのインスタンスを作成および割り当てます。

superメソッドはRack::Server.startを呼び出し、次のように定義されます。

module Rack
  class Server
    def start(&blk)
      if options[:warn]
        $-w = true
      end

      if includes = options[:include]
        $LOAD_PATH.unshift(*includes)
      end

      if library = options[:require]
        require library
      end

      if options[:debug]
        $DEBUG = true
        require "pp"
        p options[:server]
        pp wrapped_app
        pp app
      end

      check_pid! if options[:pid]

      # Touch the wrapped app, so that the config.ru is loaded before
      # daemonization (i.e. before chdir, etc).
      handle_profiling(options[:heapfile], options[:profile_mode], options[:profile_file]) do
        wrapped_app
      end

      daemonize_app if options[:daemonize]

      write_pid if options[:pid]

      trap(:INT) do
        if server.respond_to?(:shutdown)
          server.shutdown
        else
          exit
        end
      end

      server.run wrapped_app, options, &blk
    end
  end
end

Railsアプリにとって興味深い部分は、最後の行のserver.runです。ここで、再びwrapped_appメソッドに遭遇しますが、今度はもう少し探索します(すでに実行されており、そのためすでにメモ化されています)。

module Rack
  class Server
    def wrapped_app
      @wrapped_app ||= build_app app
    end
  end
end

ここでのappメソッドは次のように定義されています。

module Rack
  class Server
    def app
      @app ||= options[:builder] ? build_app_from_string : build_app_and_options_from_config
    end

    # ...

    private
      def build_app_and_options_from_config
        if !::File.exist? options[:config]
          abort "configuration #{options[:config]} not found"
        end

        app, options = Rack::Builder.parse_file(self.options[:config], opt_parser)
        @options.merge!(options) { |key, old, new| old }
        app
      end

      def build_app_from_string
        Rack::Builder.new_from_string(self.options[:builder])
      end
  end
end

options[:config]の値はデフォルトでconfig.ruになります。このファイルには次のコードが含まれています。

# This file is used by Rack-based servers to start the application.

require_relative "config/environment"

run Rails.application

ここでのRack::Builder.parse_fileメソッドは、このconfig.ruファイルの内容を取得し、次のコードを使用して解析します。

module Rack
  class Builder
    def self.load_file(path, opts = Server::Options.new)
      # ...
      app = new_from_string cfgfile, config
      # ...
    end

    # ...

    def self.new_from_string(builder_script, file = "(rackup)")
      eval "Rack::Builder.new {\n" + builder_script + "\n}.to_app",
        TOPLEVEL_BINDING, file, 0
    end
  end
end

Rack::Builderinitializeメソッドは、ここでブロックを受け取り、それをRack::Builderのインスタンス内で実行します。 ここがRailsの初期化プロセスの大部分が行われる場所です。 config.ruconfig/environment.rbrequire行が最初に実行されます:

require_relative "config/environment"

1.10 config/environment.rb

このファイルは、config.rubin/rails server)とPassengerで必要とされる共通のファイルです。ここで、これらの2つのサーバーの実行方法が結合されます。このポイントまでのすべては、RackとRailsのセットアップです。

このファイルは、config/application.rbを要求して始まります:

require_relative "application"

1.11 config/application.rb

このファイルは、config/boot.rbを要求しています:

require_relative "boot"

ただし、bin/rails serverの場合は要求されていない場合に限りますが、Passengerの場合は要求されていません。

そして、楽しいことが始まります!

2 Railsの読み込み

config/application.rbの次の行は次のようになります:

require "rails/all"

2.1 railties/lib/rails/all.rb

このファイルは、Railsのすべての個々のフレームワークを要求する責任があります:

require "rails"

%w(
  active_record/railtie
  active_storage/engine
  action_controller/railtie
  action_view/railtie
  action_mailer/railtie
  active_job/railtie
  action_cable/engine
  action_mailbox/engine
  action_text/engine
  rails/test_unit/railtie
).each do |railtie|
  begin
    require railtie
  rescue LoadError
  end
end

ここで、すべてのRailsフレームワークがロードされ、アプリケーションで利用できるようになります。それぞれのフレームワークの内部で何が起こっているかについては詳細には触れませんが、自分で探索してみることをお勧めします。

今のところ、Railsエンジン、I18n、およびRailsの設定などの共通の機能がここで定義されていることを覚えておいてください。

2.2 config/environment.rbに戻る

config/application.rbの残りの部分では、アプリケーションが完全に初期化された後に使用されるRails::Applicationの設定が定義されます。config/application.rbがRailsをロードし、アプリケーションの名前空間を定義した後、config/environment.rbに戻ります。ここで、アプリケーションはRails.application.initialize!で初期化されます。これはrails/application.rbで定義されています。

2.3 railties/lib/rails/application.rb

initialize!メソッドは次のようになります:

def initialize!(group = :default) # :nodoc:
  raise "Application has been already initialized." if @initialized
  run_initializers(group, self)
  @initialized = true
  self
end

アプリケーションは一度しか初期化できません。run_initializersメソッドを介してRailtieの初期化子が実行されます。このメソッドはrailties/lib/rails/initializable.rbで定義されています:

def run_initializers(group = :default, *args)
  return if instance_variable_defined?(:@ran)
  initializers.tsort_each do |initializer|
    initializer.run(*args) if initializer.belongs_to?(group)
  end
  @ran = true
end

run_initializersメソッド自体はややトリッキーです。Railsがここで行っているのは、すべてのクラスの祖先をトラバースして、initializersメソッドに応答するクラスを探すことです。それから祖先を名前でソートし、実行します。たとえば、Engineクラスは、initializersメソッドを提供することですべてのエンジンを利用できるようにします。

Rails::Applicationクラスは、railties/lib/rails/application.rbで定義されているように、bootstraprailtie、およびfinisherの初期化子を定義しています。bootstrapの初期化子はアプリケーションを準備します(ロガーの初期化など)、finisherの初期化子は最後に実行されます(ミドルウェアスタックの構築など)。railtieの初期化子は、Rails::Application自体で定義されている初期化子であり、bootstrapfinisherの間で実行されます。

注意:railties/lib/rails/application.rbで定義されているload_config_initializers初期化子インスタンス全体と、それに関連するconfig/initializersの設定初期化子とは異なることに注意してください。

これが終わったら、Rack::Serverに戻ります。

2.4 Rack: lib/rack/server.rb

前回は、appメソッドが定義されているところで終わりました:

module Rack
  class Server
    def app
      @app ||= options[:builder] ? build_app_from_string : build_app_and_options_from_config
    end

    # ...

    private
      def build_app_and_options_from_config
        if !::File.exist? options[:config]
          abort "configuration #{options[:config]} not found"
        end

        app, options = Rack::Builder.parse_file(self.options[:config], opt_parser)
        @options.merge!(options) { |key, old, new| old }
        app
      end

      def build_app_from_string
        Rack::Builder.new_from_string(self.options[:builder])
      end
  end
end

この時点で、appはRailsアプリケーション自体(ミドルウェア)です。次に、Rackは提供されたすべてのミドルウェアを呼び出します:

module Rack
  class Server
    private
      def build_app(app)
        middleware[options[:environment]].reverse_each do |middleware|
          middleware = middleware.call(self) if middleware.respond_to?(:call)
          next unless middleware
          klass, *args = middleware
          app = klass.new(app, *args)
        end
        app
      end
  end
end

ここで、build_appは(wrapped_appによって)Rack::Server#startの最後の行で呼び出されました。前回の状態では次のようになっていました:

server.run wrapped_app, options, &blk

この時点で、server.runの実装は使用しているサーバーによって異なります。たとえば、Pumaを使用している場合、runメソッドは次のようになります:

module Rack
  module Handler
    module Puma
      # ...
      def self.run(app, options = {})
        conf   = self.config(app, options)

        events = options.delete(:Silent) ? ::Puma::Events.strings : ::Puma::Events.stdio

        launcher = ::Puma::Launcher.new(conf, events: events)

        yield launcher if block_given?
        begin
          launcher.run
        rescue Interrupt
          puts "* Gracefully stopping, waiting for requests to finish"
          launcher.stop
          puts "* Goodbye!"
        end
      end
      # ...
    end
  end
end

サーバーの設定自体には立ち入りませんが、これがRailsの初期化プロセスの最後の部分です。

この高レベルの概要は、コードがいつ、どのように実行されるかを理解し、全体的に優れたRails開発者になるのに役立ちます。さらに詳しく知りたい場合は、おそらくRailsのソースコード自体が最適な参照先です。

フィードバック

このガイドの品質向上にご協力ください。

タイポや事実の誤りを見つけた場合は、ぜひ貢献してください。 開始するには、ドキュメントへの貢献セクションを読んでください。

不完全なコンテンツや最新でない情報も見つかるかもしれません。 メインのドキュメントに不足しているドキュメントを追加してください。 修正済みかどうかは、まずEdge Guidesを確認してください。 スタイルと規約については、Ruby on Rails Guides Guidelinesを確認してください。

修正すべき点を見つけたが、自分で修正できない場合は、 問題を報告してください

そして最後に、Ruby on Railsのドキュメントに関するあらゆる議論は、公式のRuby on Railsフォーラムで大歓迎です。