当你输入了rails server之后2

2019-11-05  本文已影响0人  will2yang
# 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
上一篇下一篇

猜你喜欢

热点阅读