このガイドでは、デフォルトの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.rb
はENV['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::Command
はnamespace
が空の場合に自動的にヘルプ出力を表示します。
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_PATH
はconfig/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.rb
のinitialize
メソッドが呼び出されます:
module Rails
class Server < ::Rack::Server
def initialize(options = nil)
@default_options = options || {}
super(@default_options)
set_environment
end
end
end
まず、super
が呼び出され、Rack::Server
のinitialize
メソッドが呼び出されます。
1.7 Rack: lib/rack/server.rb
Rack::Server
は、すべてのRackベースのアプリケーションに共通のサーバーインターフェースを提供する責任を持っており、Railsもその一部です。
Rack::Server
のinitialize
メソッドは、単純にいくつかの変数を設定します。
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::ServerCommand
のserver_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::Server
のsuper
が終了した後、rails/commands/server/server_command.rb
に戻ります。この時点で、set_environment
がRails::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_PATH
はbin/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/cache
、tmp/pids
、tmp/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::Builder
のinitialize
メソッドは、ここでブロックを受け取り、それをRack::Builder
のインスタンス内で実行します。
ここがRailsの初期化プロセスの大部分が行われる場所です。
config.ru
のconfig/environment.rb
のrequire
行が最初に実行されます:
require_relative "config/environment"
1.10 config/environment.rb
このファイルは、config.ru
(bin/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
で定義されているように、bootstrap
、railtie
、およびfinisher
の初期化子を定義しています。bootstrap
の初期化子はアプリケーションを準備します(ロガーの初期化など)、finisher
の初期化子は最後に実行されます(ミドルウェアスタックの構築など)。railtie
の初期化子は、Rails::Application
自体で定義されている初期化子であり、bootstrap
とfinisher
の間で実行されます。
注意: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フォーラムで大歓迎です。