Rails チュートリアル 1章 勉強メモ7

Rails チュートリアルの勉強メモ

railstutorial.jp

1章続き rails new 何やってんの?

  • gems/thor-0.20.3/lib/thor/command.rb
    20: def run(instance, args = [])
    21:   arity = nil
    22: 
    23:   if private_method?(instance)
    24:     instance.class.handle_no_command_error(name)
    25:   elsif public_method?(instance)
    26:     arity = instance.method(name).arity
 => 27:     instance.__send__(name, *args)
    28:   elsif local_method?(instance, :method_missing)
    29:     instance.__send__(:method_missing, name.to_sym, *args)
    30:   else
    31:     instance.class.handle_no_command_error(name)
    32:   end
    33: rescue ArgumentError => e
    34:   handle_argument_error?(instance, e, caller) ? instance.class.handle_argument_error(self, e, args, arity) : (raise e)
    35: rescue NoMethodError => e
    36:   handle_no_method_error?(instance, e, caller) ? instance.class.handle_no_command_error(name) : (raise e)
    37: end
  • ここから読みます
  • send は 第一引数のメソッドを実行する name は application

  • source_location してみる

[7] pry(#<Thor::Command>)> instance.method(:application).source_location
=> ["$HOME/.rbenv/versions/2.6.2/lib/ruby/gems/2.6.0/gems/railties-5.1.6/lib/rails/commands/application/application_command.rb", 23]
[8] pry(#<Thor::Command>)> instance.method(:perform).source_location
=> ["$HOME/.rbenv/versions/2.6.2/lib/ruby/gems/2.6.0/gems/railties-5.1.6/lib/rails/commands/application/application_command.rb", 23]

なんで・・・?

  • alias_method というものがあるらしいので original_name してみる
[14] pry(#<Thor::Command>)> instance.method(:application).original_name
=> :perform

なるほど・・・どこかで alias してるのか・・・

> export RAILS=$HOME/.rbenv/versions/2.6.2/lib/ruby/gems/2.6.0/gems/railties-5.1.6/lib/rails
> grep "alias" $RAILS/**/*.rb | grep "perform "

それっぽいものはない

> grep "perform" $RAILS/**/*.rb"

してみた

$RAILS/command/base.rb:            if meth == "perform"

怪しい

  • command/base.rb 見てみる
          # Allow the command method to be called perform.
          def create_command(meth)
            if meth == "perform"
              alias_method command_name, meth
            else
              # Prevent exception about command without usage.
              # Some commands define their documentation differently.
              @usage ||= ""
              @desc  ||= ""

              super
            end
          end

絶対これじゃん!

  • binding.pry で止めてみる
    122: def create_command(meth)
    123:   if meth == "perform"
    124:     binding.pry
 => 125:     alias_method command_name, meth
    126:   else
    127:     # Prevent exception about command without usage.
    128:     # Some commands define their documentation differently.
    129:     @usage ||= ""
    130:     @desc  ||= ""
    131: 
    132:     super
    133:   end
    134: end

[1] pry(Rails::Command::ApplicationCommand)> command_name
=> "application"

解決!!

perform をみる

  • rails/commands/application/application_command.rb
    23: def perform(*args)
 => 24:   Rails::Generators::AppGenerator.start \
    25:     Rails::Generators::ARGVScrubber.new(args).prepare!
    26: end
  • prepare! をみる
  • rails/generators/rails/app/app_generator.rb
    509: def prepare!
 => 510:   handle_version_request!(@argv.first)
    511:   handle_invalid_command!(@argv.first, @argv) do
    512:     handle_rails_rc!(@argv.drop(1))
    513:   end
    514: end
  • handle_version_request! は 引数が -v, --version だったら rails のバージョンを返す
  • 今回は new なのでスキップ

  • handle_invalid_command! をみる

    530: def handle_invalid_command!(argument, argv)
 => 531:   if argument == "new"
    532:     yield
    533:   else
    534:     ["--help"] + argv.drop(1)
    535:   end
    536: end
  • ついに new が渡された!
  • new 以外だと 引数の配列から落として ["--help"] を突っ込んでる
    • 間違った引数を入れると --help を出すって動作はここでやってたのか・・・
  • yield なので prepare! の ブロックを読む

  • handle_rails_rc! をみる

    538: def handle_rails_rc!(argv)
 => 539:   if argv.find { |arg| arg == "--no-rc" }
    540:     argv.reject { |arg| arg == "--no-rc" }
    541:   else
    542:     railsrc(argv) { |rc_argv, rc| insert_railsrc_into_argv!(rc_argv, rc) }
    543:   end
    544: end
  • argv.drop(1) してるので argv["hello_app"]
  • --no-rc をつけると railsrc を読みに行かない

    • railsrc とかあるんだ・・・
  • 次は Rails::Generators::AppGenerator.start を読む

Rails::Generators::AppGenerator.start

  • thor/base.rb
    464: def start(given_args = ARGV, config = {})
 => 465:   config[:shell] ||= Thor::Base.shell.new
    466:   dispatch(nil, given_args.dup, nil, config)
    467: rescue Thor::Error => e
    468:   config[:debug] || ENV["THOR_DEBUG"] == "1" ? (raise e) : config[:shell].error(e.message)
    469:   exit(false) if exit_on_failure?
    470: rescue Errno::EPIPE
    471:   # This happens if a thor command is piped to something like `head`,
    472:   # which closes the pipe when it's done reading. This will also
    473:   # mean that if the pipe is closed, further unnecessary
    474:   # computation will not occur.
    475:   exit(true)
    476: end
[10] pry(Rails::Generators::AppGenerator)> config
=> {:shell=>#<Thor::Shell::Color:0x00007fc3671f5f90 @always_force=false, @base=nil, @mute=false, @padding=0>}
  • dispatch(nil, given_args.dup, nil, config)

    thor/group.rb @ line 218 Thor::Group.dispatch:

    217: def dispatch(command, given_args, given_opts, config) #:nodoc:
 => 218:   if Thor::HELP_MAPPINGS.include?(given_args.first)
    219:     help(config[:shell])
    220:     return
    221:   end
    222: 
    223:   args, opts = Thor::Options.split(given_args)
    224:   opts = given_opts || opts
    225: 
    226:   instance = new(args, opts, config)
    227:   yield instance if block_given?
    228: 
    229:   if command
    230:     instance.invoke_command(all_commands[command])
    231:   else
    232:     instance.invoke_all
    233:   end
    234: end
  • --help とか入っていたら help 実行
    • handle_invalid_command! とかで --help 入っていたらここで実行
  • args, opts = Thor::Options.split(given_args)
    • オプションを切り分けてる?
- thor/parser/arguments.rb @ line 11 Thor::Arguments.split:

     8: def self.split(args)
     9:   arguments = []
    10: 
 => 11:   args.each do |item|
    12:     break if item =~ /^-/
    13:     arguments << item
    14:   end
    15: 
    16:   [arguments, args[Range.new(arguments.size, -1)]]
    17: end
  • - がついた引数、つまりオプション以降を opts に渡している
  • instance = new(args, opts, config)Rails::Generators::AppGenerator を生成
    232: def initialize(*args)
 => 233:   super
    234: 
    235:   if !options[:skip_active_record] && !DATABASES.include?(options[:database])
    236:     raise Error, "Invalid value for --database option. Supported for preconfiguration are: #{DATABASES.join(", ")}."
    237:   end
    238: 
    239:   # Force sprockets and yarn to be skipped when generating API only apps.
    240:   # Can't modify options hash as it's frozen by default.
    241:   if options[:api]
    242:     self.options = options.merge(skip_sprockets: true, skip_javascript: true, skip_yarn: true).freeze
    243:   end
    244: end
  • super 追っていくと Thor を読むことになりそうなので一旦置いとく
    • さらっと読んでみても良いかもしれない
[28] pry(Rails::Generators::AppGenerator)> instance
=> #<Rails::Generators::AppGenerator:0x00007ff0863b6db0
 @_initializer=[["hello_app"], [], {:shell=>#<Thor::Shell::Color:0x00007ff0864b6d00 @always_force=false, @base=#<Rails::Generators::AppGenerator:0x00007ff0863b6db0 ...>, @mute=false, @padding=0>}],
 @_invocations={},
 @after_bundle_callbacks=[],
 @app_path="hello_app",
 @args=[],
 @behavior=:invoke,
 @destination_stack=["/Users/mitsuki/workspace/Rails/environment"],
 @extra_entries=[],
 @gem_filter=#<Proc:0x00007ff0878feb78@/$HOME/.rbenv/versions/2.6.2/lib/ruby/gems/2.6.0/gems/railties-5.1.6/lib/rails/generators/app_base.rb:99 (lambda)>,
 @in_group=nil,
 @options=
  {"ruby"=>$HOME/.rbenv/versions/2.6.2/bin/ruby",
   "database"=>"sqlite3",
   "skip_yarn"=>false,
   "skip_gemfile"=>false,
   "skip_git"=>false,
   "skip_keeps"=>false,
   "skip_action_mailer"=>false,
   "skip_active_record"=>false,
   "skip_puma"=>false,
   "skip_action_cable"=>false,
   "skip_sprockets"=>false,
   "skip_spring"=>false,
   "skip_listen"=>false,
   "skip_coffee"=>false,
   "skip_javascript"=>false,
   "skip_turbolinks"=>false,
   "skip_test"=>false,
   "skip_system_test"=>false,
   "dev"=>false,
   "edge"=>false,
   "no_rc"=>false,
   "api"=>false,
   "skip_bundle"=>false},
 @shell=#<Thor::Shell::Color:0x00007ff0864b6d00 @always_force=false, @base=#<Rails::Generators::AppGenerator:0x00007ff0863b6db0 ...>, @mute=false, @padding=0>>
  • block_given? = false, command = nil なので, 次は instance.invoke_all
- thor/invocation.rb @ line 133 Thor::Invocation#invoke_all:

    132: def invoke_all #:nodoc:
 => 133:   self.class.all_commands.map { |_, command| invoke_command(command) }
    134: end
  • all_commands は, Thorクラスとサブクラスのすべてのコマンドを返す
      # Returns the commands for this Thor class and all subclasses.
      #
      # ==== Returns
      # OrderedHash:: An ordered hash with commands names as keys and Thor::Command
      #               objects as values.
      #
      def all_commands
        @all_commands ||= from_superclass(:all_commands, Thor::CoreExt::OrderedHash.new)
        @all_commands.merge!(commands)
      end
  • all_commands の command をひとつづ取り出して invoke_command(command) で実行!

  • 疲れたので今回は一旦ここで終わり

    • 次はall_commandsの最初の set_default_accessors! から読んでいきます
  • ちなみに all_commands の結果

[2] pry(#<Rails::Generators::AppGenerator>)> self.class.all_commands
=> {"set_default_accessors!"=>#<struct Thor::Command name="set_default_accessors!", description=nil, long_description=nil, usage=nil, options={}, ancestor_name=nil>,
 "create_root"=>#<struct Thor::Command name="create_root", description=nil, long_description=nil, usage=nil, options={}, ancestor_name=nil>,
 "create_root_files"=>#<struct Thor::Command name="create_root_files", description=nil, long_description=nil, usage=nil, options={}, ancestor_name=nil>,
 "create_app_files"=>#<struct Thor::Command name="create_app_files", description=nil, long_description=nil, usage=nil, options={}, ancestor_name=nil>,
 "create_bin_files"=>#<struct Thor::Command name="create_bin_files", description=nil, long_description=nil, usage=nil, options={}, ancestor_name=nil>,
 "create_config_files"=>#<struct Thor::Command name="create_config_files", description=nil, long_description=nil, usage=nil, options={}, ancestor_name=nil>,
 "create_boot_file"=>#<struct Thor::Command name="create_boot_file", description=nil, long_description=nil, usage=nil, options={}, ancestor_name=nil>,
 "create_active_record_files"=>#<struct Thor::Command name="create_active_record_files", description=nil, long_description=nil, usage=nil, options={}, ancestor_name=nil>,
 "create_db_files"=>#<struct Thor::Command name="create_db_files", description=nil, long_description=nil, usage=nil, options={}, ancestor_name=nil>,
 "create_lib_files"=>#<struct Thor::Command name="create_lib_files", description=nil, long_description=nil, usage=nil, options={}, ancestor_name=nil>,
 "create_log_files"=>#<struct Thor::Command name="create_log_files", description=nil, long_description=nil, usage=nil, options={}, ancestor_name=nil>,
 "create_public_files"=>#<struct Thor::Command name="create_public_files", description=nil, long_description=nil, usage=nil, options={}, ancestor_name=nil>,
 "create_test_files"=>#<struct Thor::Command name="create_test_files", description=nil, long_description=nil, usage=nil, options={}, ancestor_name=nil>,
 "create_system_test_files"=>#<struct Thor::Command name="create_system_test_files", description=nil, long_description=nil, usage=nil, options={}, ancestor_name=nil>,
 "create_tmp_files"=>#<struct Thor::Command name="create_tmp_files", description=nil, long_description=nil, usage=nil, options={}, ancestor_name=nil>,
 "create_vendor_files"=>#<struct Thor::Command name="create_vendor_files", description=nil, long_description=nil, usage=nil, options={}, ancestor_name=nil>,
 "delete_app_assets_if_api_option"=>#<struct Thor::Command name="delete_app_assets_if_api_option", description=nil, long_description=nil, usage=nil, options={}, ancestor_name=nil>,
 "delete_app_helpers_if_api_option"=>#<struct Thor::Command name="delete_app_helpers_if_api_option", description=nil, long_description=nil, usage=nil, options={}, ancestor_name=nil>,
 "delete_application_layout_file_if_api_option"=>#<struct Thor::Command name="delete_application_layout_file_if_api_option", description=nil, long_description=nil, usage=nil, options={}, ancestor_name=nil>,
 "delete_public_files_if_api_option"=>#<struct Thor::Command name="delete_public_files_if_api_option", description=nil, long_description=nil, usage=nil, options={}, ancestor_name=nil>,
 "delete_js_folder_skipping_javascript"=>#<struct Thor::Command name="delete_js_folder_skipping_javascript", description=nil, long_description=nil, usage=nil, options={}, ancestor_name=nil>,
 "delete_assets_initializer_skipping_sprockets"=>#<struct Thor::Command name="delete_assets_initializer_skipping_sprockets", description=nil, long_description=nil, usage=nil, options={}, ancestor_name=nil>,
 "delete_application_record_skipping_active_record"=>#<struct Thor::Command name="delete_application_record_skipping_active_record", description=nil, long_description=nil, usage=nil, options={}, ancestor_name=nil>,
 "delete_action_mailer_files_skipping_action_mailer"=>#<struct Thor::Command name="delete_action_mailer_files_skipping_action_mailer", description=nil, long_description=nil, usage=nil, options={}, ancestor_name=nil>,
 "delete_action_cable_files_skipping_action_cable"=>#<struct Thor::Command name="delete_action_cable_files_skipping_action_cable", description=nil, long_description=nil, usage=nil, options={}, ancestor_name=nil>,
 "delete_non_api_initializers_if_api_option"=>#<struct Thor::Command name="delete_non_api_initializers_if_api_option", description=nil, long_description=nil, usage=nil, options={}, ancestor_name=nil>,
 "delete_api_initializers"=>#<struct Thor::Command name="delete_api_initializers", description=nil, long_description=nil, usage=nil, options={}, ancestor_name=nil>,
 "delete_new_framework_defaults"=>#<struct Thor::Command name="delete_new_framework_defaults", description=nil, long_description=nil, usage=nil, options={}, ancestor_name=nil>,
 "delete_bin_yarn_if_skip_yarn_option"=>#<struct Thor::Command name="delete_bin_yarn_if_skip_yarn_option", description=nil, long_description=nil, usage=nil, options={}, ancestor_name=nil>,
 "finish_template"=>#<struct Thor::Command name="finish_template", description=nil, long_description=nil, usage=nil, options={}, ancestor_name=nil>,
 "apply_rails_template"=>#<struct Thor::Command name="apply_rails_template", description=nil, long_description=nil, usage=nil, options={}, ancestor_name=nil>,
 "run_bundle"=>#<struct Thor::Command name="run_bundle", description=nil, long_description=nil, usage=nil, options={}, ancestor_name=nil>,
 "run_webpack"=>#<struct Thor::Command name="run_webpack", description=nil, long_description=nil, usage=nil, options={}, ancestor_name=nil>,
 "generate_spring_binstubs"=>#<struct Thor::Command name="generate_spring_binstubs", description=nil, long_description=nil, usage=nil, options={}, ancestor_name=nil>,
 "run_after_bundle_callbacks"=>#<struct Thor::Command name="run_after_bundle_callbacks", description=nil, long_description=nil, usage=nil, options={}, ancestor_name=nil>}