詳解!Ruby on Railsの初期化プロセス - railsコマンドからアプリケーションの起動まで

Railsの初期化プロセスは複雑で、数多くのステップが絡み合っています。この記事では、その複雑さをステップバイステップで確認していくことで、Railsの初期化順序を理解できます。そして、Railsの初期化にまつわる改善やバグ解消を行いやすくなるでしょう。
Ruby on Railsのバージョンは、7.0.5で確認しています。また、Rails の初期化プロセス - Railsガイドも大いに参考にしています。

Railsの初期化プロセスの全体的な流れ

Ruby on Railsの初期化時の「bin/rails serverからアプリケーションサーバの起動まで」のステップは次のようになっています。重要な処理の部分に絞ることで、厳密性よりも理解のしやすさを重視しています。

Step 1. railsコマンドの実行

まずは、ターミナルからrailsコマンドを実行します。

$ bin/rails server

bin/railsファイル内は次のようになってますconfig/boot.rbではrequire "bundler/setup"require "bootsnap/setup"が呼ばれ、それぞれGemfile内のgemのセットアップ(※gemのロードはされない)とbootsnap(ブート時間を短くするためgem)のセットアップがされます。また、rails/commandsではserverconsoleなど引数に応じたコマンドクラスが実行されるようになっています。

#!/usr/bin/env ruby
APP_PATH = File.expand_path("../config/application", __dir__)

#config/boot.rbを読み込む
#https://github.com/rails/rails/blob/v7.0.5/railties/lib/rails/generators/rails/app/templates/config/boot.rb.tt
require_relative "../config/boot"

#引数に応じてrailsコマンドを実行させる
#https://github.com/rails/rails/blob/v7.0.5/railties/lib/rails/commands.rb
require "rails/commands"

Step 2.config/application.rbの読み込み

rails/commandsの処理をたどっていくと、bin/railsコマンドの引数にserverを指定しているので、Rails::Command::ServerCommand#performが実行されます。performメソッド内のrequire APP_PATHによりconfig/application.rbが読み込まれます。

#https://github.com/rails/rails/blob/v7.0.5/railties/lib/rails/commands/server/server_command.rb#L129-L148
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|
          # NOTE: APP_PATH には /path/to/config/application が設定されている
          # そのため、 config/application.rb が読み込まれる
          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(options[:using])
          end
        end
      end

Step 2.1. config/application.rb内でRailsの初期化処理の登録

config/application.rbの先頭部分でrequire "rails/all"が読み込まれます。

# config/application.rb

require_relative "boot"

require "rails/all"

# ...

rails/all.rbのファイルは次のようになっており、各railtieやengineが読み込まれます。各railtieやengineのファイル内ではinitializerメソッドで初期化処理の登録が行われます。初期化処理の実行自体はRails.application.initialize!時に行われるのでここではあくまで初期化処理の登録にとどまっています。

#rails/all.rb
#https://github.com/rails/rails/blob/v7.0.5/railties/lib/rails/all.rb

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

initializerのサンプルとしてactive_record/railtie.rb内では次のように初期化処理を登録しています。

#https://github.com/rails/rails/blob/v7.0.5/activerecord/lib/active_record/railtie.rb#L301-L316

initializer "active_record.clear_active_connections" do
  config.after_initialize do
    # ActiveRecord::Baseが読み込まれたときに実行される
    ActiveSupport.on_load(:active_record) do
      # アクティブなコネクションを切断する
      clear_active_connections!
      flush_idle_connections!
    end
  end
end

Step 2.2. config/application.rb内でGemのロード

次にconfig/application.rbを読み進めていくと、Bundler.require(*Rails.groups)が実行されます。ここでGemfile内のgemがロードされます。

# config/application.rb

require_relative "boot"

require "rails/all"

# :test, :development, :production のグループに応じて
# Gemfileのgemがロードされる
Bundler.require(*Rails.groups)

# ...

Step 2.3. config/application.rb内でRails::Applicationクラスのロード

そして、config/application.rbを最後まで読み進めると Rails::Applicationを継承した自分のApplicationクラスがロードされます。

# config/application.rb

require_relative "boot"

require "rails/all"

Bundler.require(*Rails.groups)

# Rails::Applicationを継承した自分のApplicationクラスがロードされる
module RailsInitializatoinTest
  class Application < Rails::Application
    # Initialize configuration defaults for originally generated Rails version.
    config.load_defaults 7.0

    # Configuration for the application, engines, and railties goes here.
    #
    # These settings can be overridden in specific environments using the files
    # in config/environments, which are processed later.
    #
    # config.time_zone = "Central Time (US & Canada)"
    # config.eager_load_paths << Rails.root.join("extras")
  end
end

Step 3. サーバーを起動させる

config/application.rbをロードできたので、Rails::Command::ServerCommand#performに戻り、サーバーを起動させます。

# https://github.com/rails/rails/blob/v7.0.5/railties/lib/rails/commands/server/server_command.rb#L129-L148
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 APP_PATH
          Dir.chdir(Rails.application.root)

          if server.serveable?
            # 「Booting Puma...」がコンソールに表示される
            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(options[:using])
          end
        end
      end

Step 4. config.ruの実行

サーバー起動の中ではconfig.ruファイルを読み込み、Rackアプリケーションとして実行します。
config.ruの中身は次のようになっており、config/environment.rbを読み込んでRailsの初期化処理を実行し、サーバーを起動させています。

# config.ru

# アプリケーションを起動させるためにRackベースのサバーとして利用されるファイル

# config/environment.rbを読み込む
require_relative "config/environment"

# サーバーを起動させる
run Rails.application
Rails.application.load_server

参考: Rails と Rack - Railsガイド

Step 4.1. config.ruからRailsの初期化処理の実行

config.ruから呼ばれるconfig/enviromnent.rbファイル内では、Railsアプリケーションの読み込みとRailsアプリケーションの初期化をします。
Railsアプリケーションの読み込みは、既に前の方の処理で読み込んでいるためスルーされます。
Railsアプリケーションの初期化は Rails.application.initialize! が呼ばれ、各railtieやengineのファイル内で登録された初期化処理が実行されます。

# config/environment.rb

# Railsアプリケーションを読み込む
# 前の方の処理で既に読み込んでいるためスルー
require_relative "application"

# Railsアプリケーションを初期化する
Rails.application.initialize!

また、この初期化処理の実行時に次のようなことが起こります。

  • 各railtieやengineファイル内のinitializerメソッドの実行
  • config/initializers/*.rbのファイルの読み込み
  • ActiveSupport.run_load_hooks(:active_record, Base)などの登録されてい登録されていた(※production環境の場合)
  • config/routes.rbファイルの読み込み
  • などなど

Step 4.2. config.ruからアプリケーションサーバの起動

最後に、config.rurun Rails.applicationRails.application.load_serverによりアプリケーションサーバ(ex Pumaなど)の設定ファイルを読み込み、サーバーが起動します。

# config.ru

# ...

# サーバーを起動させる
run Rails.application
Rails.application.load_server

参考: bin/rails s 時のファイルの読み込み順序ログ

bin/rails serverコマンドを実行したときの流れは次のようになっています。

$ RAILS_ENV=production bin/rails s

# bin/rails ファイルの読み込み
#  L require_relative "../config/boot"
#  |  L require "bundler/setup"
#  |  L require "bootsnap/setup"
#  Lrequire "rails/commands"
#     L config/application.rb ファイル
#     |  L require_relative "boot"
#     |  L require "rails/all" # */railtieが読み込まれてinitializerが登録される、active_record.rb などの読み込まれる。Baseは呼ばれないのでActiveSupport.run_load_hooksは実行されない
#     L run Bundler.require(*Rails.groups) # Gemが読み込まれる
#     L 「Booting Puma...」がコンソールに表示される
#     L config.ru ファイル
#        L require_relative "config/environment"
#        |  L require_relative "application" # 既に読み込み済みなのでスルー
#        |  L Rails.application.initialize! # 登録済みのinitializerを実行する、各gemのhookが登録される
#        |     L load config/initializers/*.rb files
#        |     L  ActiveSupport.run_load_hooks(:active_record, Base) ※development環境の場合は呼ばれない
#        |     L  load config/routes.rb file
#        L run Rails.application # Railsアプリケーションがrackとして起動
#        L Rails.application.load_server # Rack::Handler::Puma
#          L load config/puma.rb file
#          L 「Puma starting in single mode...」がコンソールに表示される
````