Ruby on RailsのScaffoldの仕組み
Scaffoldをはじめ、Ruby on Railsに組み込まれているコードジェネレータについて詳しく書かれた日本語の情報は少ないです。英語のドキュメントとしてはAPIドキュメントのModule Rails::Generatorが参考になりますが、やはりRuby on Railsのソースコードを読むのが一番分かりやすいです。
ScaffoldジェネレータのソースはRubyがC:¥Rrubyにインストールされている場合、C:¥Ruby¥lib¥ruby¥gems¥1.8¥gems¥rails-2.3.2¥lib¥rails_generator¥generators¥components¥scaffold¥scaffold_generator.rbになります。このファイルと同じディレクトリにあるtemplatesディレクトリの下に、生成するコードの元になるファイルが置かれています。
テンプレート
templatesディレクトリにあるview_show.html.erbを見てみましょう
<% for attribute in attributes -%> <p> <b><%= attribute.column.human_name %>:</b> <%%=h @<%= singular_name %>.<%= attribute.name %> %> </p> <% end -%> <%%= link_to 'Edit', edit_<%= singular_name %>_path(@<%= singular_name %>) %> | <%%= link_to 'Back', <%= plural_name %>_path %>
これは詳細表示用のテンプレートで第1回の例ではapp/views/players/show.html.erbファイルがこのテンプレートから作られています。app/views/players/show.html.erbの一部を見てみましょう。
<p> <b>Name:</b> <%=h @player.name %> </p> <p> <b>Team:</b> <%=h @player.team %> </p> ・・・ 省略 ・・・ <b>Assist:</b> <%=h @player.assist %> </p> <%= link_to 'Edit', edit_player_path(@player) %> | <%= link_to 'Back', players_path %>
view_show.html.erb内の<%% ~ %>タグはそのまま<% ~ %>タグに置き換わっています。ただし<% ~ %>の内容が評価されてテンプレートを展開しています。またplural_name、singular_name、attributesなどの変数が使われているのが分かります。
Scaffoldジェネレータ
Scaffoldジェネレータ本体scaffold_generator.rbを上から見てみましょう。ScaffoldGeneratorクラスは、ジェネレータの元になるRails::Generator::NamedBaseクラスを継承しています。また、ジェネレータで使われるコントローラ名、そのファイル名などの属性(インスタンス変数)が定義されています。
class ScaffoldGenerator < Rails::Generator::NamedBase default_options :skip_timestamps => false, :skip_migration => false, :force_plural => false attr_reader :controller_name, :controller_class_path, :controller_file_path, ・・・ 省略 ・・・
初期化メソッドinitializeでは、ジェネレータで使われるコントローラ名、そのファイル名などの設定が行われます。
def initialize(runtime_args, runtime_options = {}) super if @name == @name.pluralize && !options[:force_plural] logger.warning "Plural version of the model detected, using singularized version. Override with --force-plural." @name = @name.singularize end @controller_name = @name.pluralize base_name, @controller_class_path, @controller_file_path, @controller_class_nesting, @controller_class_nesting_depth = extract_modules(@controller_name) ・・・ 省略 ・・・ end
manifestメソッドがジェネレータのメインです。ここでは、m.class_collisions()でこれから作成するファイルがダブらないかチェックし、m.directory()で必要なディレクトリーを作成し、m.template()でテンプレートを元に必要なファイルを作成しています。
また、m.route_resourcesのようにconfig/route.rbを変更する専用のメソッドなどもあります。さらに、Scaffoldではモデルの作成部分はm.dependency 'model'……でmodelジェネレータを呼び出しています。
def manifest record do |m| # Check for class naming collisions. m.class_collisions("#{controller_class_name}Controller", "#{controller_class_name}Helper") m.class_collisions(class_name) # Controller, helper, views, test and stylesheets directories. m.directory(File.join('app/models', class_path)) m.directory(File.join('app/controllers', controller_class_path)) m.directory(File.join('app/helpers', controller_class_path)) ・・・ 省略 ・・・ for action in scaffold_views m.template( "view_#{action}.html.erb", File.join('app/views', controller_class_path, controller_file_name, "#{action}.html.erb") ) end # Layout and stylesheet. m.template('layout.html.erb', File.join('app/views/layouts', controller_class_path, "#{controller_file_name}.html.erb")) m.template('style.css', 'public/stylesheets/scaffold.css') m.template( 'controller.rb', File.join('app/controllers', controller_class_path, "#{controller_file_name}_controller.rb") ) ・・・ 省略 ・・・ m.route_resources controller_file_name m.dependency 'model', [name] + @args, :collision => :skip end end