generate scaffoldコマンド

generate scaffold実行
generate scaffoldで、scaffoldメソッドが動的に行っていた処理をファイルに書き出してみる。
C:\Projects\OtherProjects\Rails\bookmark>jruby script\generate scaffold item
exists app/controllers/
exists app/helpers/
create app/views/items
exists app/views/layouts/
exists test/functional/
dependency model
exists app/models/
exists test/unit/
exists test/fixtures/
identical app/models/item.rb
identical test/unit/item_test.rb
identical test/fixtures/items.yml
Connection refused

と、MySQLが起動していなかったようだ。もう一回やると…
C:\Projects\OtherProjects\Rails\bookmark>jruby script\generate scaffold item
exists app/controllers/
exists app/helpers/
exists app/views/items
exists app/views/layouts/
exists test/functional/
dependency model
exists app/models/
exists test/unit/
exists test/fixtures/
identical app/models/item.rb
identical test/unit/item_test.rb
identical test/fixtures/items.yml
create app/views/items/_form.rhtml
create app/views/items/list.rhtml
create app/views/items/show.rhtml
create app/views/items/new.rhtml
create app/views/items/edit.rhtml
create app/controllers/items_controller.rb
create test/functional/items_controller_test.rb
create app/helpers/items_helper.rb
create app/views/layouts/items.rhtml
create public/stylesheets/scaffold.css

生成されたファイル(createで始まる行)だけ抜き出してみると、以下のようになる。
  • app/views/items/_form.rhtml
  • app/views/items/list.rhtml
  • app/views/items/show.rhtml
  • app/views/items/new.rhtml
  • app/views/items/edit.rhtml
  • app/controllers/items_controller.rb
  • test/functional/items_controller_test.rb
  • app/helpers/items_helper.rb
  • app/views/layouts/items.rhtml
  • public/stylesheets/scaffold.css
今まではitem_controllerというので処理を受けていたんだけども、作られたものはitemsになっている。Controllerの名前は、単数だろうが複数だろうがどっちでもいいんだろうか?とりあえずは、動作確認。
C:\Projects\OtherProjects\Rails\bookmark>jruby script\server
=> Booting WEBrick...
=> Rails application started on http://0.0.0.0:3000
=> Ctrl-C to shutdown server; call with --help for options
[2007-11-17 15:26:10] INFO WEBrick 1.3.1
[2007-11-17 15:26:10] INFO ruby 1.8.5 (2007-11-01) [java]
[2007-11-17 15:26:10] INFO WEBrick::HTTPServer#start: pid=1102920 port=3000
http://localhost:3000/itemにアクセスしてみると、今までどおりの画面。
つづいて、http://localhost:3000/itemsにアクセスしてみる。画面自体はまったく同じだが、よーくみるとタイトルが変更されている。
http://localhost:3000/item -> Scaffolding
http://localhost:3000/items -> Items: index
とりあえずは無事に動いているようだ。

generate scaffoldはどうやって動いている?
ここからは、肝心のgenerate scaffoldを追っていきたい。script\generateの中をみてみると、

#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../config/boot'
require 'commands/generate'

となっている。config/bootは初期設定なので、生成処理自体はcommands/generateにぶん投げられているようだ。というわけで、
%RUBY_HOME%\lib\ruby\gems\1.8\gems\rails-1.2.5\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::Generator::Scripts::Generatorに飛んでいるようだ。Generatorは…
require File.dirname(__FILE__) + '/../scripts'

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

というわけで結局、Rails::Generator::Scripts::Baseにたどりついた。実際に開いてrunメソッドをみてみると、いろいろ事前チェックはするものの、最終的には

Rails::Generator::Base.instance(options[:generator], args, options).command(options[:command]).invoke!
の部分で生成スクリプトを呼び出している。
Rails::Generator::Baseは、RailsのGeneratorフレームワークを利用する際に継承するための基底クラスになっているようで、実際にScaffoldGeneratorはこのクラス(のサブクラスのNamedBase)を継承している。

RailsのGeneratorフレームワーク
Railsのコード自動生成部分はフレームワーク的な構造になっており、命名規約に従った形で(オプションで変更可能)自動生成スクリプトを作成・配置することで、generateコマンドから呼び出すことができるようになっている。scaffoldコマンドもこのうちの1つという位置づけだ。詳細はRails::GeneratorモジュールのRDocに書いてあるが、実際にGeneratorが格納されているフォルダをながめてみるとだいたいの仕様はわかるはず。Generatorスクリプトは
%RUBY_HOME%\lib\ruby\gems\1.8\gems\rails-1.2.5\lib\rails_generator\generators\components
に配置されていて、「script\generate コマンド名」のコマンド名ごとにサブフォルダが作成されている。自分の環境に存在しているサブフォルダは、
  • controller
  • integration_test
  • mailer
  • migration
  • model
  • observer
  • plugin
  • resource
  • scaffold
  • scaffold_resource
  • session_migration
  • web_service
の12種類。また、これとは別にcomponentsと同レベルにapplicationという名前のディレクトリが存在し、application全体を生成する際にはこれが使われているようだ。
個別スクリプト用のサブディレクトリをみてみると、直下に「コマンド名_generator.rb」という名前でGeneratorフレームワークから呼び出される自動生成のRubyスクリプトが配置されており、templatesサブフォルダにソースのテンプレートが格納される形になっている。

scaffoldコマンドの場合は、components\scaffoldディレクトリの直下にscaffold_generator.rbがあり、templatesサブフォルダ内には
  • controller.rb
  • form.rhtml
  • form_scaffolding.rhtml
  • functional_test.rb
  • helper.rb
  • layout.rhtml
  • style.css
  • view_edit.rhtml
  • view_list.rhtml
  • view_new.rhtml
  • view_show.rhtml
の11種類のテンプレートが格納されている。先ほど生成されたファイルと対応していることが確認できる。

自分で自動生成コマンドを作る時は、これらのルールにあわせてファイルを配置すれば簡単に使えるようになりそうだ。実際の開発に適用するにあたって、もう少しドメインに特化したスクリプトを生成したくなった場合に役立つと思われる。

Controllerの名前
Controllerの命名規約についてだが、USAGEに参考になる情報が載っていた。
The scaffold generator creates a controller to interact with a model.
If the model does not exist, it creates the model as well. The generated
code is equivalent to the "scaffold :model" declaration, making it easy
to migrate when you wish to customize your controller and views.

The generator takes a model name, an optional controller name, and a
list of views as arguments. Scaffolded actions and views are created
automatically. Any views left over generate empty stubs.

The scaffolded actions and views are:
index, list, show, new, create, edit, update, destroy

If a controller name is not given, the plural form of the model name
will be used. The model and controller names may be given in CamelCase
or under_score and should not be suffixed with 'Model' or 'Controller'.
Both model and controller names may be prefixed with a module like a
file path; see the Modules Example for usage.
これによると、scaffoldコマンドにコントローラー名を与えなかった場合には、モデル名の複数形が使われるようだ。コントローラー名を明示的に与えることもできるので、コントローラー名はモデル名となんの関係もなくても良さそうだ。
わざわざこんな場所のUSAGEファイルをみなくても、引数なしでコマンドをたたくとUSAGEが表示されるらしい…最初からそうしておけばよかった。

次のステップ
次は、scaffoldが生成した処理がどういうものなのかを一つずつみていく。

コメント