その他
    ホーム技術発信DoRubyScaffoldはどこからくるの? 後編

    Scaffoldはどこからくるの? 後編

    この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

    こんにちは。
    KBMJのエンジニアの高瀬です。

    大分間が空いてしまいましたが、前回の続きを書く事にします。
    前回は、http://doruby.kbmj.com/zenpou_on_rails/20071227/Scaffold___

    前回はscaffoldを作って以下の様なファイルが出来ました。
         create  app/views/members
          create  app/views/members/index.html.erb
          create  app/views/members/show.html.erb
          create  app/views/members/new.html.erb
          create  app/views/members/edit.html.erb
          create  app/views/layouts/members.html.erb
          create  public/stylesheets/scaffold.css
          create    app/models/member.rb
          create    test/unit/member_test.rb
          create    test/fixtures/members.yml
          create    db/migrate
          create    db/migrate/001_create_members.rb
          create  app/controllers/members_controller.rb
          create  test/functional/members_controller_test.rb
          create  app/helpers/members_helper.rb

    こちら、どこから来るのかを調べて行こうと思います。

    結論を先に述べると、以下のパスにテンプレートが存在します。
    {railsのインストール先}/lib/rails_generator/generators/components/scaffold/
    これがどのような仕組みになっているかを追いかけて行きます。

    前回、scaffoldは以下のコマンドによって生成されました。

          ruby script/generate scaffold Member name:string discription:text birthday:datetime
    rubyの構文はruby –helpによって出してみたところ
        Usage: ruby [switches] [–] [programfile] [arguments]

          -0[octal]       specify record separator (\0, if no argument)
          -a              autosplit mode with -n or -p (splits $_ into $F)
          -c              check syntax only
          -Cdirectory     cd to directory, before executing your script
          -d              set debugging flags (set $DEBUG to true)
          -e ‘command’    one line of script. Several -e’s allowed. Omit [programfile]
          -Fpattern       split() pattern for autosplit (-a)
          -i[extension]   edit ARGV files in place (make backup if extension supplied)
          -Idirectory     specify $LOAD_PATH directory (may be used more than once)
          -Kkcode         specifies KANJI (Japanese) code-set
          -l              enable line ending processing
          -n              assume ‘while gets(); … end’ loop around your script
          -p              assume loop like -n but print line also like sed
          -rlibrary       require the library, before executing your script
          -s              enable some switch parsing for switches after script name
          -S              look for the script using PATH environment variable
          -T[level]       turn on tainting checks
          -v              print version number, then turn on verbose mode
          -w              turn warnings on for your script
          -W[level]       set warning level; 0=silence, 1=medium, 2=verbose (default)
          -x[directory]   strip off text before #!ruby line and perhaps cd to directory
          –copyright     print the copyright
          –version       print the version


    となっています。
    今回の場合、      “script/generate”[

    がprogramfileで      “scaffold Member name:string discription:text birthday:datetime”
    がargumentsになります。


    というわけで、プログラムファイルの内容を見て見ましょう。
    script/generateの中身はこんな内容になっています。      #!/usr/bin/env ruby
          require File.dirname(__FILE__) + ‘/../config/boot’
          require ‘commands/generate’


    require は、ライブラリの呼び出しを行います。
    ここで、      require File.dirname(__FILE__) + ‘/../config/boot’


    は、プロジェクトのrailsの呼び出しを行っているRailsの基本的な所ですので、      require ‘commands/generate’


    を見てみる事にします。
    {Railsのインストール先}/lib/commands/generate.rb
    を参照しましょう。      require “#{RAILS_ROOT}/config/environment”
          require ‘rails_generator’
          require ‘rails_generator/scripts/generate’

          ARGV.shift if [‘–help’, ‘-h’].include?(ARGV[0])
          Rails::Generator::Scripts::Generate.new.run(ARGV)


    Railsのenvironment(環境の設定等)を呼び出し、rails_generatorとrails_generator/scripts/generateを呼び出して、
    います。

    ちょっと文章がソースばかりになるので、省略しますが、      require ‘rails_generator’


    のrails_generator.rbにて、      require ‘rails_generator/base’
          require ‘rails_generator/lookup’
          require ‘rails_generator/commands’
          require ‘rails_generator/simple_logger’


    など、generatorのファイルを呼び出しています。
    そして、lookupによって下記のフォルダにあるファイルをgeneratorのコマンドに取り込んでいます。      #{::RAILS_ROOT}/lib/generators
          #{::RAILS_ROOT}/vendor/generators
          #{::RAILS_ROOT}/vendor/plugins/*/**/generators
          #{::RAILS_ROOT}/vendor/plugins/*/**/rails_generators
          #{Dir.user_home}/.rails/generators
          #{File.dirname(__FILE__)}/generators/components


    そして実行段階。/lib/commands/generate.rbの中で実行されていたrunコマンドを見てみます。      Rails::Generator::Scripts::Generate.new.run(ARGV)


    クラス名Rails::Generator::Scripts::Generateなのでrails_generator/scripts/generate.rbを見に行きます。
    /lib/commands/generate.rbで3行目でrequireされてましたね。
    見てみると      require File.dirname(__FILE__) + ‘/../scripts’

          module Rails::Generator::Scripts
            class Generate < Base
              mandatory_options :command => :create
            end
          end

    というコードになっています。
    run呼び出してるのにrunがない……。
    Rails::Generator::Scriptsを拡張してるみたいです。
    いったりきたりで大変ですね。

    というわけでrails_generator/scripts.rbにいきます。        def run(args = [], runtime_options = {})
              begin
                parse!(args.dup, runtime_options)
              rescue OptionParser::InvalidOption => e
                # Don’t cry, script. Generators want what you think is invalid.
              end

              # Generator name is the only required option.
              unless options[:generator]
                usage if args.empty?
                options[:generator] ||= args.shift
              end

              # Look up generator instance and invoke command on it.
              Rails::Generator::Base.instance(options[:generator], args, options).command(options[:command]).invoke!
            rescue => e
              puts e
              puts ”  #{e.backtrace.join(“\n  “)}\n” if options[:backtrace]
              raise SystemExit
            end
    長いのでrunのコードだけ採用しています。
    Rails::Generator::Base.instance(options[:generator], args, options).command(options[:command]).invoke!
    にてジェネレータを見つけ、lookupでロードしたgenaratorの manifestを呼び出しています。
    ここに関するコマンドはlib/rails_generator/commands.rbなどに定義されているのでそこから掘っていってください。

    というわけで、送った”scaffold Member name:string discription:text birthday:datetime”の最初のscaffoldの指定により、
    scaffoldを呼び出します。

    ということで、lookupの中のソースコード一覧の中で、scaffoldのソースコードが存在するのは
    {railsのインストール先}/lib/rails_generator/generators/components/scaffold/
    でしたので、scafolldはここから来ている事がわかりました。

    今回、
    Rails::Generator::Base.instance(options[:generator], args, options).command(options[:command]).invoke!
    を検索している際、ヒットした
    http://d.hatena.ne.jp/elm200/20070718/1184736852を途中から参考にさせていただきました。

    というか、既にこちらのエントリがあったという事で、今回のエントリは二番煎じだったかな……と思いましたが、

    とりあえず後悔公開CHUです。