当你输入了rails server之后2
# bin/rails
begin
load File.expand_path('../spring', __FILE__)
rescue LoadError => e
raise unless e.message.include?('spring')
end
APP_PATH = File.expand_path('../config/application', __dir__)
require_relative '../config/boot'
require 'rails/commands'
1.首先载入spring,这里不做了解. 如果这里加载了spring那么下面的代码就不会执行了。
2.设置好APP_PATH之后的rails/commands里面会使用到.
3.加载 ../config/boot 文件
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.
而 bundler/setup 的作用主要是激活BUNDLE_GEMFILE文件所需要的gem依赖包,这里不做了解.然后就是require 'rails/commands':
# frozen_string_literal: true
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
这里做的主要还是替换别称,然后调用:
Rails::Command.invoke 'server', ARGV
module Rails
module Command
class << self
# Receives a namespace, arguments and the behavior to invoke the command.
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
def find_by_namespace(namespace, command_name = nil) # :nodoc:
lookups = [ namespace ]
lookups << "#{namespace}:#{command_name}" if command_name
lookups.concat lookups.map { |lookup| "rails:#{lookup}" }
lookup(lookups)
namespaces = subclasses.index_by(&:namespace)
namespaces[(lookups & namespaces.keys).first]
end
end
end
end
Rails寻找namespace类似于Thor的,它只有一条规则:
命令名必须以“_command.rb”结尾。这是必需的,因为Rails根据加载路径来寻找文件,并且在在使用前才加载.
通过这几个命名空间 :webrat, :rails, :integration
将寻找以下命令:
"rails:webrat", "webrat:integration", "webrat"
autoload :Behavior
autoload :Base
首先看 behavior,找到并且require command文件:
def lookup(namespaces)
paths = namespaces_to_paths(namespaces)
paths.each do |raw_path|
lookup_paths.each do |base|
path = "#{base}/#{raw_path}_#{command_type}"
begin
require path
return
rescue LoadError => e
raise unless e.message =~ /#{Regexp.escape(path)}$/
rescue Exception => e
warn "[WARNING] Could not load #{command_type} #{path.inspect}. Error: #{e.message}.\n#{e.backtrace.join("\n")}"
end
end
end
end
之后看base, 用这个方法会将ServerCommand加入到subclassed里:
def inherited(base) #:nodoc:
super
if base.name && base.name !~ /Base$/
Rails::Command.subclasses << base
end
end
最后就是我们这个 server_command的perform了
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)
server.start
end
end
将工作目录设置成rails app的根目录
def set_application_directory!
Dir.chdir(File.expand_path("../..", APP_PATH)) unless File.exist?(File.expand_path("config.ru"))
end
如果是重启项目那么清空pid
def prepare_restart
FileUtils.rm_f(options[:pid]) if options[:restart]
end
实例化Rails:Server 设置好server的options 和 environment
# lib/rails/commands/server/server_command
def server_options
{
user_supplied_options: user_supplied_options,
server: @server,
log_stdout: @log_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
# lib/rails/commands/server/server_command
def initialize(options = nil)
@default_options = options || {}
super(@default_options)
set_environment
end
# lib/rack/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
# lib/rails/commands/server/server_command
def set_environment
ENV["RAILS_ENV"] ||= options[:environment]
end
加载 rails/config/application
require_relative 'boot'
require 'rails/all'
# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
Bundler.require(*Rails.groups)
module RailsApp
class Application < Rails::Application
# Initialize configuration defaults for originally generated Rails version.
config.load_defaults 5.2
# Settings in config/environments/* take precedence over those specified here.
# Application configuration can go into files in config/initializers
# -- all .rb files in that directory are automatically loaded after loading
# the framework and any gems in your application.
end
end
rails/all require rails 所需要的基础组件
# frozen_string_literal: true
require "rails"
%w(
active_record/railtie
active_storage/engine
action_controller/railtie
action_view/railtie
action_mailer/railtie
active_job/railtie
action_cable/engine
rails/test_unit/railtie
sprockets/railtie
).each do |railtie|
begin
require railtie
rescue LoadError
end
end
Bundler.require(*Rails.groups) 根据rails运行的环境require相应的gem
在Application继承Rails::Application的时候会调用钩子方法inherited:
1.设置rails的app_class
2.将rails app目录下的lib目录加入到$LOAD_PATH
3.执行钩子before_configuration方法
# lib/rails/application
def inherited(base)
super
Rails.app_class = base
add_lib_to_load_path!(find_root(base.called_from))
ActiveSupport.run_load_hooks(:before_configuration, base)
end
如果base不是ABSTRACT_RAILTIES里的类,为了方便,将其加入到eager_load_namespaces里,并且根据caller_locations调用栈设置好called_from
# lib/rails/engine
def inherited(base)
unless base.abstract_railtie?
Rails::Railtie::Configuration.eager_load_namespaces << base
base.called_from = begin
call_stack = caller_locations.map { |l| l.absolute_path || l.path }
File.dirname(call_stack.detect { |p| p !~ %r[railties[\w.-]*/lib/rails|rack[\w.-]*/lib/rack] })
end
end
super
end
ABSTRACT_RAILTIES = %w(Rails::Railtie Rails::Engine Rails::Application)
def abstract_railtie?
ABSTRACT_RAILTIES.include?(name)
end
将继承railtie的类都加入到subclasses里方便后面的操作:
# lib/rails/railtie
def inherited(base)
unless base.abstract_railtie?
subclasses << base
end
end
def application
@application ||= (app_class.instance if app_class)
end
def config #:nodoc:
@config ||= Application::Configuration.new(self.class.find_root(self.class.called_from))
end