tamuraです。
Railsわからなすぎなので追ってみました。 バージョンは5.2あたりだった気がします。
目標は Rails.application
って何?がわかること。
bin/rails
#!/usr/bin/env ruby
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'
rails/commands.rb
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
なので実行コマンドはこうなる。
Rails::Command.invoke "server", []
rails/command.rb
invoke
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
namespace = "server"
command_name = "server"
なのでこうなる。
command = find_by_namespace("server", "server")
find_by_namespace
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
lookups
は最終的にこうなる。
["server", "server:server", "rails:server", "rails:server:server"]
rails/command/behavir.rb
lookup
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
paths
は ["server, "server/server", "rails/server", "rails/server/server"]
lookup_paths
は %( rails/commands commands )
command_type
は "command"
なので
- 1回目 :
require "rails/commands/server_command"
=> 失敗 - 2回目 :
require "commands/server_command"
=> 失敗 - 3回目 :
require "rails/commands/server/server_command"
=> 成功!
rails/commands.rb
find_by_namespace
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
subclasses
は Rails::Command::Base.inherited
で設定している
rails/command/base.rb
inherited
def inherited(base) #:nodoc:
super
if base.name && base.name !~ /Base$/
Rails::Command.subclasses << base
end
end
ServerCommand
が読み込まれたとき、そのClass
をsubclasses
に入れ込んでいる。
rails/commands.rb
find_by_namespace
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
index_by(&:namespace)
のnamespace
は
rails/command/base.rb
def namespace(name = nil)
if name
super
else
@namespace ||= super.chomp("_command").sub(/:command:/, ":")
end
end
とあって、Rails::Command::ServerCommand
はThor
を継承しているので、
rails:command:server_command
chomp -> rails:command:server
sub -> rails:server
となる。 なので以下の様になる。
namespaces = {'rails:server': Rails::Command::ServerCommand}
namespaces[(lookups & namespaces.keys).first] => Rails::Command::ServerCommand
find_by_namespace
終了。
rails/command.rb
invoke
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
command = Rails::Command::ServerCommand
command.all_commands["server"]
は "server"=>#<struct Thor::Command name="server"
が返ってくるのでif
はtrue
になる。
Rails::Command::ServerCommand.perform("server", [], nil)
これはインスタンスメソッドではなくクラスメソッド。
rails/command/base.rb
perform
def perform(command, args, config) # :nodoc:
if Rails::Command::HELP_MAPPINGS.include?(args.first)
command, args = "help", []
end
dispatch(command, args.dup, nil, config)
end
dispatch
は Thor
のメソッド。
thor.rb
dispatch
def dispatch(meth, given_args, given_opts, config) #:nodoc: # rubocop:disable MethodLength
meth ||= retrieve_command_name(given_args)
command = all_commands[normalize_command_name(meth)]
if !command && config[:invoked_via_subcommand]
# We're a subcommand and our first argument didn't match any of our
# commands. So we put it back and call our default command.
given_args.unshift(meth)
command = all_commands[normalize_command_name(default_command)]
end
if command
args, opts = Thor::Options.split(given_args)
if stop_on_unknown_option?(command) && !args.empty?
# given_args starts with a non-option, so we treat everything as
# ordinary arguments
args.concat opts
opts.clear
end
else
args = given_args
opts = nil
command = dynamic_command_class.new(meth)
end
opts = given_opts || opts || []
config[:current_command] = command
config[:command_options] = command.options
instance = new(args, opts, config)
yield instance if block_given?
args = instance.args
trailing = args[Range.new(arguments.size, -1)]
instance.invoke_command(command, trailing || [])
end
いろいろやっているけど、
instance = new(args, opts, config)
でRails::Command::ServerCommand
をインスタンス化している。
instance.invoke_command(command, trailing || [])
ここでインスタンスメソッドを実行している。
(command
は #<struct Thor::Command name="server", description="", long_description=nil, usage="", options={}, ancestor_name=nil>
)
thor/invocation.rb
invoke_command
def invoke_command(command, *args) #:nodoc:
current = @_invocations[self.class]
unless current.include?(command.name)
current << command.name
command.run(self, *args)
end
end
command
は #<struct Thor::Command name="server", description="", long_description=nil, usage="", options={}, ancestor_name=nil>
thor/command.rb
run
def run(instance, args = [])
arity = nil
if private_method?(instance)
instance.class.handle_no_command_error(name)
elsif public_method?(instance)
arity = instance.method(name).arity
instance.__send__(name, *args)
elsif local_method?(instance, :method_missing)
instance.__send__(:method_missing, name.to_sym, *args)
else
instance.class.handle_no_command_error(name)
end
rescue ArgumentError => e
handle_argument_error?(instance, e, caller) ? instance.class.handle_argument_error(self, e, args, arity) : (raise e)
rescue NoMethodError => e
handle_no_method_error?(instance, e, caller) ? instance.class.handle_no_command_error(name) : (raise e)
end
public_method?
がtrue
になるのでinstance.__send__(name, *args)
を実行する。
rails/commands/server/server_commands.rb
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
ここにきてサーバが起動される。
set_application_directory!
- ディレクトリを移動する
prepare_restart
- pidファイルを削除する
initialize
def initialize(options = nil)
@default_options = options || {}
super(@default_options)
set_environment
end
引数のoptions
はServerCommand.server_options
で定義済み。
set_environment
は以下の通り。
def set_environment
ENV["RAILS_ENV"] ||= options[:environment]
end
perform
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
require APP_PATH
のAPP_PATH
はconfig/application.rb
へのパス。
(ここのconfig/application.rb
は自分のRailsアプリケーションのやつ)
config/application.rb
module MyApp
class Application < Rails::Application
config.load_defaults 5.2
end
end
こうなっていて、rails/application.rb
でこう定義されていて、
module Rails
class Application < Engine
class << self
def inherited(base)
Rails.app_class = base'
...snip...
end
end
end
end
rails.rb
でこう定義されているので、
module Rails
class << self
@application = @app_class = nil
attr_writer :application
attr_accessor :app_class, :cache, :logger
def application
@application ||= (app_class.instance if app_class)
end
end
end
Rails.application
は Rails::Application
を継承した自分のアプリのApplication
ということがわかった。
不明点
Rails::Command::ServerCommand
に"server"
っていうコマンドはいつ追加されたの?instance.__send__("server", *args)
でなんでいきなりRails::Command::ServerCommand
のperform
が実行されるの?