エンジニアとしての市場価値を測りませんか?PR

企業からあなたに合ったオリジナルのスカウトを受け取って、市場価値を測りましょう

13
15

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

rails new の流れを追う

Last updated at Posted at 2014-05-08

環境

  • ruby 2.1.1p76
  • Rails 4.1.1

railsコマンド

ふとrails newした時の流れが気になったので・・・
まずはrailsコマンドの在処を調べて、ファイルの中身を確認します。

$ which rails
/Users/usutani/.rvm/gems/ruby-2.1.1@rails411/bin/rails
$ cat `which rails`

ファイル先頭のコメント部を確認すると・・・

path/to/bin/rails
#!/usr/bin/env ruby_executable_hooks
#
# このファイルはRubyGemsによって生成されました。
#
# アプリケーションrailtiesはgemの一部としてインストールされ、
# このファイルはその実行を容易にするためここにあります。
#

とのこと。ファイルの続きは・・・

path/to/bin/rails
require 'rubygems'

version = ">= 0"

if ARGV.first
  str = ARGV.first
  str = str.dup.force_encoding("BINARY") if str.respond_to? :force_encoding
  if str =~ /\A_(.*)_\z/ and Gem::Version.correct?($1) then
    version = $1
    ARGV.shift
  end
end

gem 'railties', version
load Gem.bin_path('railties', 'rails', version)

if文のブロックでrails _4.1.1_ newのように第一引数にバージョンが指定された場合に対応する。

指定したバージョンのrailtiesが無ければGem::LoadErrorで終了する。

最後に、バージョンを指定してrailtiesのrailsをロードする。requireでないのは二度目以降も読み込むため。

バージョンを指定しない場合の振る舞いをirbで試してみると・・・

irb
> gem 'railties', ">= 0"
=> true
> load Gem.bin_path('railties', 'rails', ">= 0")
Usage:
  rails new APP_PATH [options]
...
> Gem.bin_path('railties', 'rails', ">= 0")
=> "/Users/usutani/.rvm/gems/ruby-2.1.1@rails411/gems/railties-4.1.1/bin/rails"

ロードしているrailtiesのbinディレクトリに存在するrailsファイルの中身を確認します。

railties-4.1.1/bin/rails
#!/usr/bin/env ruby

git_path = File.expand_path('../../../.git', __FILE__)

if File.exist?(git_path)
  railties_path = File.expand_path('../../lib', __FILE__)
  $:.unshift(railties_path)
end
require "rails/cli"

railtiesディレクトリの上位に .git ディレクトリが存在すれば、自身を優先してロードされるように探索パス$:(組み込み変数)にlibを追加してから、rails/cliをロードしています。

railties/lib

cli.rb

lib/rails/cli.rb
require 'rails/app_rails_loader'

# アプリのrailsコマンドで実行した場合は、残りのスクリプトは実行されません。
Rails::AppRailsLoader.exec_app_rails

今はアプリ外のrailsコマンドを実行しているので、残り(下記)のスクリプトが実行されます。

lib/rails/cli.rb
require 'rails/ruby_version_check'
Signal.trap("INT") { puts; exit(1) }

if ARGV.first == 'plugin'
  ARGV.shift
  require 'rails/commands/plugin'
else
  require 'rails/commands/application'
end

Ctrl + Cが押されたら、プログラムを異常終了する。
rails newの場合なので、else節に入りrails/commands/applicationがロードされる。

Generators

続けてrails/commands/application.rbを確認します。

rails/commands/application.rb
require 'rails/generators'
require 'rails/generators/rails/app/app_generator'

module Rails
  module Generators
    class AppGenerator # :nodoc:
      # We want to exit on failure to be kind to other libraries
      # This is only when accessing via CLI
      def self.exit_on_failure?
        true
      end
    end
  end
end

args = Rails::Generators::ARGVScrubber.new(ARGV).prepare!
Rails::Generators::AppGenerator.start args

Generatorは、Rails 3.0からThorをベースにしています。

exit_on_failure?をオーバライドして、失敗時に異常終了させる。
引数を準備して、Generators::AppGeneratorを開始する。

app_generator.rbの外観は次のようになります:

rails/generators/rails/app/app_generator.rb
require 'rails/generators/app_base'

module Rails
  module ActionMethods # :nodoc:
...
  # application builderはapplication generatorの要素を上書きできます。
  #
  # Gemfile、READMEやJavaScriptファイルの作成のなどのように、あなたは全体の操作を上書きでき
  # ます。これらの操作を正確に把握していなくても、別のテンプレートアクションを作成できます。
  class AppBuilder
...
  module Generators
...
    class AppGenerator < AppBase # :nodoc:
...

    # このクラスはAppGeneratorが呼ばれる前に引数を準備します。このクラスはバージョンやヘルプ情
    # 報を求められれば提供し、(追加の設定オプションに用いる)railsrcファイルも構成します。
    # 
    # このクラスはARGVを設定し正しく変化させるため、AppGeneratorの開始前に、このクラスを呼び
    # 出す必要があります。
    class ARGVScrubber # :nodoc
...
      def prepare!

AppBuilder
このクラスのメソッドで、アプリ内のディレクトリ作成やファイルのコピーなどを行っています。

AppGenerator
AppBuilderとの関係が気になるので調べてみると、get_builder_classメソッドからAppBuilderを呼び出していました。

rails/generators/rails/app/app_generator.rb
  module Generators
...
    class AppGenerator < AppBase # :nodoc:
...
      def get_builder_class
        defined?(::AppBuilder) ? ::AppBuilder : Rails::AppBuilder
      end

このget_builder_classを呼んでいるのは、親クラスAppBaseです。

rails/generators/app_base.rb
module Rails
  module Generators
    class AppBase < Base # :nodoc:
...
    protected
...
      def builder
        @builder ||= begin
          builder_class = get_builder_class
          builder_class.send(:include, ActionMethods)
          builder_class.new(self)
        end
      end

これらの関係を図示すると次のようになります:

generators.png

Thor::Groupを派生したAppGeneratorクラスは、定義されたメソッドを順番に実行していきます。

rails/generators/rails/app/app_generator.rb
      def create_root_files
      def create_app_files
      def create_bin_files
      def create_config_files
      def create_boot_file
      def create_active_record_files
      ...

create_root_filesメソッド内のbuildの呼び出しに注目してみると、

rails/generators/rails/app/app_generator.rb
      def create_root_files
        build(:readme)
        ...

buildはAppBaseで定義されていて、builder=AppBuilderのreadmeを呼んでいます。

rails/generators/app_base.rb
      def build(meth, *args)
        builder.send(meth, *args) if builder.respond_to?(meth)
      end
rails/generators/rails/app/app_generator.rb
  class AppBuilder
...
    def readme
      copy_file "README.rdoc", "README.rdoc"
    end
13
15
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
15

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?