Rails の初期化プロセス

本章は、Rails初期化プロセスの内部について解説します。上級Rails開発者向けに推奨される、きわめて高度な内容を扱っています。

このガイドの内容:

  • bin/rails serverの利用法
  • Rails初期化シーケンスのタイムライン
  • 起動シーケンスで通常以外のファイルが必要な場所
  • Rails::Serverインターフェイスの定義方法と利用法

本ガイドでは、デフォルトのRailsアプリケーションでRuby on Railsスタックの起動時に必要なすべてのメソッド呼び出しについて詳しく解説します。具体的には、bin/rails serverを実行してアプリケーションを起動するとどのようなことが行われているかに注目して解説します。

特に記載のない限り、文中に記載されるRuby on Railsアプリケーションへのパスは相対パスです。

Railsのソースコードを参照しながら読み進めるのであれば、GitHubページのtキーバインドでfile finderを起動するとその場でファイルを素早く検索できます。

1 起動!

それではアプリケーションを起動して初期化を開始しましょう。Railsアプリケーションの起動は、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ファイルは、Railsアプリケーションの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. require "bootsnap/setup" # Speed up boot time by caching expensive operations. 

標準的なRailsアプリケーションにはGemfileというファイルがあり、アプリケーション内のすべての依存関係がそのファイル内で宣言されています。config/boot.rbはGemfileの位置をENV['BUNDLE_GEMFILE']に設定します。Gemfileが存在する場合、bundler/setuprequireします。このrequireは、Gemfileの依存ファイルが置かれている読み込みパスをBundlerで設定するときに使われます。

1.3 rails/commands.rb

config/boot.rbの実行が完了すると、次にコマンドのエイリアスを拡張するrails/commandsrequireします。この状況では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が渡されると、ここで定義されているaliasesの中からマッチするコマンドを探します。

1.4 rails/command.rb

Railsコマンドを入力すると、invokeが指定の名前空間内でコマンドを探索し、見つかった場合はそのコマンドを実行します。

コマンドがRailsによって認識されない場合は、Rakeが引き継いで同じ名前でタスクを実行します。

以下のソースコードでわかるように、namespaceが空の場合、Rails::Commandは自動的にヘルプを出力します。

 module Rails module Command class << self def invoke(full_namespace, args = [], **config) args = ["--help"] if rails_new_with_no_path?(args) full_namespace = full_namespace.to_s namespace, command_name = split_namespace(full_namespace) command = find_by_namespace(namespace, command_name) with_argv(args) do if command && command.all_commands[command_name] command.perform(command_name, args, config) else invoke_rake(full_namespace, args, config) end end rescue UnrecognizedCommandError => error if error.name == full_namespace && command && command_name == full_namespace command.perform("help", [], config) else puts error.detailed_message end exit(1) end end end end 

serverコマンドが指定されると、Railsはさらに以下のコードを実行します。

 module Rails module Command class ServerCommand < Base # :nodoc: def perform 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(options[:using]) end end end end end end 

上のファイルは、config.ruファイルが見つからない場合に限り、Railsのルートディレクトリ(config/application.rbを指すAPP_PATHから2階層上のディレクトリ)に移動します。 これによって、次はRails::Serverクラスが起動されます。

1.5 actionpack/lib/action_dispatch.rb

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

1.6 rails/commands/server/server_command.rb

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

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

最初にsuperが呼び出され、そこからRackup::Serverinitializeメソッドを呼び出します。

1.7 Rackup: lib/rackup/server.rb

Rackup::Serverは、あらゆるRackベースのアプリケーション向けに共通のサーバーインターフェイスを提供する役割を担います(RailsもRackアプリケーションの一種です)。

Rackup::Serverinitializeは、いくつかの変数を設定するだけの簡単なメソッドです。

 module Rackup class Server def initialize(options = nil) @ignore_options = [] if options @use_default_options = false @options = options @app = options[:app] if options[:app] else @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 < Base # :nodoc: no_commands do def server_options { user_supplied_options: user_supplied_options, server: options[: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インスタンス変数に代入されます。

superRackup::Serverの中で完了すると、rails/commands/server_command.rbに制御が戻ります。この時点で、set_environmentRails::Serverオブジェクトのコンテキスト内で呼び出されます。

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

initializeが完了すると、サーバーコマンドに制御が戻り、そこでAPP_PATH(先ほど設定済み)がrequireされます。

1.8 config/application

require APP_PATHが実行されると、続いてconfig/application.rbが読み込まれます(APP_PATHbin/railsで定義されていることを思い出しましょう)。この設定ファイルはRailsアプリケーションの中にあり、必要に応じて自由に変更できます。

1.9 Rails::Server#start

config/applicationの読み込みが完了すると、server.startが呼び出されます。このメソッド定義は以下のようになっています。

 module Rails class Server < ::Rackup::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, STDERR, STDOUT) Rails.logger.broadcast_to(console) end end end end 

このメソッドはINTシグナルのトラップを作成するので、CTRL-Cキーを押したときにサーバープロセスが終了するようになります。 コードに示されているように、ここではtmp/cachetmp/pidstmp/socketsディレクトリが作成されます。bin/rails server--dev-cachingオプションを指定して呼び出した場合は、development環境でのキャッシュをオンにします。最後にwrapped_appが呼び出されます。このメソッドは、ActiveSupport::Loggerのインスタンスの作成と代入の前に、Rackアプリケーションを作成する役割を担います。

superメソッドはRackup::Server.startを呼び出します。このメソッド定義の冒頭は以下のようになっています。

 module Rackup class Server def start(&block) if options[:warn] $-w = true end if includes = options[:include] $LOAD_PATH.unshift(*includes) end Array(options[:require]).each do |library| 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, &block) end end end 

Railsアプリケーションとして興味深い部分は、最終行のserver.runでしょう。ここでもwrapped_appメソッドが再び使われています。今度はこのメソッドをもう少し詳しく調べてみましょう(既に一度実行されてメモ化済みですが)。

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

このappメソッドの定義は以下のようになっています。

 module Rackup 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 Rack::Builder.parse_file(self.options[:config]) end def build_app_from_string Rack::Builder.new_from_string(self.options[:builder]) end end end 

options[:config]の値はデフォルトではconfig.ruです。config.ruの内容は以下のようになっています。

 # This file is used by Rack-based servers to start the application. require_relative "config/environment" run Rails.application Rails.application.load_server 

上のコードのRack::Builder.parse_fileメソッドは、このconfig.ruファイルの内容を受け取って、以下のコードで解析(parse)します。

 module Rack class Builder def self.load_file(path, **options) # ... new_from_string(config, path, **options) end # ... def self.new_from_string(builder_script, path = "(rackup)", **options) builder = self.new(**options) # We want to build a variant of TOPLEVEL_BINDING with self as a Rack::Builder instance. # We cannot use instance_eval(String) as that would resolve constants differently. binding = BUILDER_TOPLEVEL_BINDING.call(builder) eval(builder_script, binding, path) builder.to_app end end end 

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

 require_relative "config/environment" 

1.10 config/environment.rb

このファイルは、config.rurails server)とPassengerの両方でrequireされるコマンドファイルです。サーバーを実行するためのこれら2種類の方法はここで合流します。ここより前の部分はすべてRackとRailsの設定です。

このファイルの冒頭部分ではconfig/application.rbrequireします。

 require_relative "application" 

1.11 config/application.rb

このファイルはconfig/boot.rbrequireします。

 require_relative "boot" 

ただし、それまでrequireされていなかった場合に限り、bin/rails serverの場合にboot.rbがrequireされます。ただしPassengerを使う場合はboot.rbをrequireしません

ここからいよいよ面白くなってきます。

2 Railsを読み込む

config/application.rbの次の行は以下のようになっています。

 require "rails/all" 

2.1 railties/lib/rails/all.rb

このファイルはRailsのすべてのフレームワークをrequireする役目を担当します。

 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 

アプリケーションは一度だけ初期化できます。railties/lib/rails/initializable.rbで定義されているrun_initializersメソッドによって、Railtieのさまざまなイニシャライザが実行されます。

 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ファイルで定義されており、その中でbootstraprailtiefinisherイニシャライザをそれぞれ定義しています。 bootstrapイニシャライザは、ロガーの初期化などアプリケーションの準備を行います 最後に実行されるfinisherイニシャライザは、ミドルウェアスタックのビルドなどを行います。 railtieイニシャライザはRails::Application自身で定義されており、bootstrapfinisherの間に実行されます。

Railtieイニシャライザ全体と、load_config_initializersイニシャライザのインスタンスやそれに関連するconfig/initializers以下のイニシャライザ設定ファイルを混同しないようにしましょう。

以上の処理が完了すると、制御はRackup::Serverに移ります。

2.4 Rack: lib/rack/server.rb

これまで進んだのは、以下のappメソッドが定義されている部分まででした。

 module Rackup 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 Rack::Builder.parse_file(self.options[:config]) end def build_app_from_string Rack::Builder.new_from_string(self.options[:builder]) end end end 

このコードのappとは、Railsアプリケーション自身(ミドルウェアの一種)であり、ここから先は、提供されているすべてのミドルウェアをRackが呼び出します。

 module Rackup 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 

Rackup::Server#startの最終行で、build_appが(wrapped_appによって)呼び出されていたことを思い出しましょう。最後に見たときのコードは以下のようになっていました。

 server.run(wrapped_app, **options, &block) 

このserver.runの実装は、アプリケーションで使うWebサーバーによって異なります。たとえばPumaを使う場合のrunメソッドは以下のようになります。

 module Rack module Handler module Puma # ... def self.run(app, options = {}) conf = self.config(app, options) log_writer = options.delete(:Silent) ? ::Puma::LogWriter.strings : ::Puma::LogWriter.stdio launcher = ::Puma::Launcher.new(conf, log_writer: log_writer, 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のソースコードそのものを読むのがおそらくベストです。

3 関連リンク

フィードバックについて

Railsガイドは GitHub の yasslab/railsguides.jp で管理・公開されております。本ガイドを読んで気になる文章や間違ったコードを見かけたら、気軽に Pull Request を出して頂けると嬉しいです。Pull Request の送り方については GitHub の README をご参照ください。

原著における間違いを見つけたら『Rails のドキュメントに貢献する』を参考にしながらぜひ Rails コミュニティに貢献してみてください 🛠💨✨

本ガイドの品質向上に向けて、皆さまのご協力が得られれば嬉しいです。

Railsガイド運営チーム (@RailsGuidesJP)

支援・協賛

Railsガイドは下記の協賛企業から継続的な支援を受けています。支援・協賛にご興味あれば協賛プランからお問い合わせいただけると嬉しいです。

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