本指南介紹了每個方法呼叫
需要在 Rails 堆疊上為預設的 Rails 啟動 Ruby
應用程式,沿途詳細解釋每個部分。為了這
指南,我們將關注執行 bin/rails server
時會發生什麼
啟動您的應用程式。
除非另有說明,本指南中的 NOTE: 路徑與 Rails 或 Rails 應用程式相關。
提示:如果您想在瀏覽 Rails 源
code,建議使用t
key 繫結開啟GitHub裡面的檔案查詢器,查詢檔案
迅速地。
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
檔案就是我們應用中的 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
。 Bundler 使用 require 來
為您的 Gemfile 的依賴項設定載入路徑。
一個標準的 Rails 應用程式依賴於幾個 gem,特別是:
- actioncable
- actionmailer
- actionpack
- actionview
- 主動工作
- activemodel
- 活動記錄
- 主動儲存
- 主動支援
- action郵箱
- actiontext
- 阿雷爾
- 建造者
- 捆綁器
- erubi
- i18n
- 郵件
- 啞劇型別
- rack
- rack-測試
- rails
- railties
- 耙子
- sqlite3
- 雷神
- 資訊
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
如果我們使用 s
而不是 server
,Rails 將使用 aliases
定義here來查詢匹配的命令。
1.4 rails/command.rb
當鍵入 Rails 命令時,invoke
嘗試查詢給定的命令
名稱空間並在找到時執行命令。
如果 Rails 不能識別該命令,它會將控制權交給 Rake 運行同名任務。
如圖所示,如果 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 根目錄(一個路徑向上兩個目錄
來自指向 config/application.rb
的 APP_PATH
),但前提是
未找到 config.ru
檔案。然後啟動 Rails::Server
類。
1.5 actionpack/lib/action_dispatch.rb
Action Dispatch 是 Rails 框架的路由元件。 它添加了路由、session 和通用中介軟體等功能。
1.6 rails/commands/server/server_command.rb
Rails::Server
類是在這個檔案中定義的,繼承自
Rack::Server
。當呼叫 Rails::Server.new
時,這會呼叫 initialize
rails/commands/server/server_command.rb
中的方法:
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
的 value 將分配給 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
value 將被分配給實例變數 @options
。
super
在 Rack::Server
中完成後,我們跳回
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
目錄。然後在開發中啟用快取
如果用 --dev-caching
呼叫 bin/rails server
。最後,它呼叫 wrapped_app
是
負責在建立和分配實例之前建立 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]
value 預設為 config.ru
,其中包含:
# 這個檔案被根據 Rack 的伺服器用來啟動應用程式。
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所需的通用檔案。這就是這兩種執行伺服器的方式相遇的地方;在此之前的所有內容都已設定為 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
sprockets/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
您只能初始化一個應用程式一次。 Railtie 初始化器
通過 run_initializers
方法執行,該方法定義在
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
和 finishers
之間執行。
注意: 不要將 Railtie 初始化器整體與 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
請記住,在 Rack::Server#start
的最後一行呼叫了 build_app
(由 wrapped_app
)。
這是我們離開時的樣子:
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 初始化過程中的最後一段旅程。
這個高層次的 overview 將幫助您瞭解您的程式碼何時 執行和如何,並總體上成為一個更好的 Rails 開發人員。如果你 還想了解更多,Rails原始碼本身大概就是 接下來最好的去處。
回饋
我們鼓勵您幫助提高本指南的品質。
如果您發現任何拼寫錯誤或資訊錯誤,請提供回饋。 要開始回饋,您可以閱讀我們的 回饋 部分。
您還可能會發現不完整的內容或不是最新的內容。 請務必為 main 新增任何遺漏的文件。假設是 非穩定版指南(edge guides) 請先驗證問題是否已經在主分支上解決。 請前往 Ruby on Rails 指南寫作準則 查看寫作風格和慣例。
如果由於某種原因您發現要修復的內容但無法自行修補,請您 提出 issue。
關於 Ruby on Rails 的任何類型的討論歡迎提供任何文件至 rubyonrails-docs 討論區。