Rails 2.0のgenerateで複数形/単数形の違いを確認してみる

モデルは単数形でテーブル名は複数形だったかなとうろ覚えだったのだが、いざ使おうとするとやっぱり複数形/単数形の使い分けができてなかった。ちゃんと確認しておくメモ。

比較用のプロジェクトを生成

rails single
rails plural

モデルの生成結果を比較

モデル名をtagとtagsとした場合を比較。

(cd single; script/generate model tag name:string)
(cd plural; script/generate model tags name:string)
diff -r single plural

セッションキー等の固有情報以外では、以下の2ファイルが違うことがわかった。

# diff -c single/app/models/tag.rb plural/app/models/tags.rb 
*** single/app/models/tag.rb    2007-12-19 22:08:23.000000000 +0900
--- plural/app/models/tags.rb   2007-12-19 22:08:35.000000000 +0900
***************
*** 1,2 ****
! class Tag < ActiveRecord::Base
  end
--- 1,2 ----
! class Tags < ActiveRecord::Base
  end
# diff -c single/test/unit/tag_test.rb plural/test/unit/tags_test.rb
*** single/test/unit/tag_test.rb        2007-12-19 22:08:23.000000000 +0900
--- plural/test/unit/tags_test.rb       2007-12-19 22:08:35.000000000 +0900
***************
*** 1,6 ****
  require File.dirname(__FILE__) + '/../test_helper'

! class TagTest < ActiveSupport::TestCase
    # Replace this with your real tests.
    def test_truth
      assert true
--- 1,6 ----
  require File.dirname(__FILE__) + '/../test_helper'

! class TagsTest < ActiveSupport::TestCase
    # Replace this with your real tests.
    def test_truth
      assert true

逆に、db/migration/001_create_tags.rb が両方にでき、内容は同じとなる。

コントローラの生成結果を比較

(cd single; script/generate controller tag)
(cd plural; script/generate controller tags)

こんどはコントローラとそのテストとヘルパが作られる。

# diff -c single/app/controllers/tag_controller.rb plural/app/controllers/tags_controller.rb 
*** single/app/controllers/tag_controller.rb    2007-12-19 22:29:16.000000000 +0900
--- plural/app/controllers/tags_controller.rb   2007-12-19 22:29:27.000000000 +0900
***************
*** 1,2 ****
! class TagController < ApplicationController
  end
--- 1,2 ----
! class TagsController < ApplicationController
  end
# diff -c single/test/functional/tag_controller_test.rb plural/test/functional/tags_controller_test.rb 
*** single/test/functional/tag_controller_test.rb       2007-12-19 22:29:16.000000000 +0900
--- plural/test/functional/tags_controller_test.rb      2007-12-19 22:29:27.000000000 +0900
***************
*** 1,6 ****
  require File.dirname(__FILE__) + '/../test_helper'
  
! class TagControllerTest < ActionController::TestCase
    # Replace this with your real tests.
    def test_truth
      assert true
--- 1,6 ----
  require File.dirname(__FILE__) + '/../test_helper'
  
! class TagsControllerTest < ActionController::TestCase
    # Replace this with your real tests.
    def test_truth
      assert true
# diff -c single/app/helpers/tag_helper.rb plural/app/helpers/tags_helper.rb 
*** single/app/helpers/tag_helper.rb    2007-12-19 22:29:16.000000000 +0900
--- plural/app/helpers/tags_helper.rb   2007-12-19 22:29:27.000000000 +0900
***************
*** 1,2 ****
! module TagHelper
  end
--- 1,2 ----
! module TagsHelper
  end

至って平和ですなあ。

scaffoldの生成結果を比較

では、scaffoldでどうなるか。実は、ここでちとハマったので確認しておきたかったというのがこれまでの流れの背景。

rm -rf single plural
rails single
rails plural
(cd single; script/generate scaffold tag name:string)
(cd plural; script/generate scaffold tags name:string)
diff -r single plural

まず、以下のファイルは上記で確認した結果と同じ。

Only in single/test/unit: tag_test.rb
Only in plural/test/unit: tags_test.rb
Only in single/app/models: tag.rb
Only in plural/app/models: tags.rb

次、コントローラはファイル名が同じだが、内容ではモデルの単数形/複数形が違う。これに伴ってインスタンス変数の名前も違う。

diff -r single/app/controllers/tags_controller.rb plural/app/controllers/tags_controller.rb
5c5
<     @tags = Tag.find(:all)
---
>     @tags = Tags.find(:all)
16c16
<     @tag = Tag.find(params[:id])
---
>     @tags = Tags.find(params[:id])
20c20
<       format.xml  { render :xml => @tag }
---
>       format.xml  { render :xml => @tags }
27c27
<     @tag = Tag.new
---
>     @tags = Tags.new
31c31
<       format.xml  { render :xml => @tag }
---
>       format.xml  { render :xml => @tags }
37c37
<     @tag = Tag.find(params[:id])
---
>     @tags = Tags.find(params[:id])
43c43
<     @tag = Tag.new(params[:tag])
---
>     @tags = Tags.new(params[:tags])
46,49c46,49
<       if @tag.save
<         flash[:notice] = 'Tag was successfully created.'
<         format.html { redirect_to(@tag) }
<         format.xml  { render :xml => @tag, :status => :created, :location => @tag }
---
>       if @tags.save
>         flash[:notice] = 'Tags was successfully created.'
>         format.html { redirect_to(@tags) }
>         format.xml  { render :xml => @tags, :status => :created, :location => @tags }
52c52
<         format.xml  { render :xml => @tag.errors, :status => :unprocessable_entity }
---
>         format.xml  { render :xml => @tags.errors, :status => :unprocessable_entity }
60c60
<     @tag = Tag.find(params[:id])
---
>     @tags = Tags.find(params[:id])
63,65c63,65
<       if @tag.update_attributes(params[:tag])
<         flash[:notice] = 'Tag was successfully updated.'
<         format.html { redirect_to(@tag) }
---
>       if @tags.update_attributes(params[:tags])
>         flash[:notice] = 'Tags was successfully updated.'
>         format.html { redirect_to(@tags) }
69c69
<         format.xml  { render :xml => @tag.errors, :status => :unprocessable_entity }
---
>         format.xml  { render :xml => @tags.errors, :status => :unprocessable_entity }
77,78c77,78
<     @tag = Tag.find(params[:id])
<     @tag.destroy
---
>     @tags = Tags.find(params[:id])
>     @tags.destroy

次にビューを見ると、インスタンス変数の名前の違いのほかに、ビューのおかれているディレクトリ名が両方とも複数形であることに気づく。コントローラ名が複数形に自動変換されている、ということらしい。手動でコントローラを作成した場合は複数形へ変換されなかったことからすると、挙動が違うことになる。

さらに驚くのが、複数形でscaffoldした場合の index.html.erb がエラーで動かないことだ。これは link_to で使う 〜_path の形のメソッドあるいは変数が未定義であることによる。ということで、scaffoldの引数に複数形を指定するとハマることになるようだ。

diff -r single/app/views/tags/edit.html.erb plural/app/views/tags/edit.html.erb
1c1
< <h1>Editing tag</h1>
---
> <h1>Editing tags</h1>
3c3
< <%= error_messages_for :tag %>
---
> <%= error_messages_for :tags %>
5c5
< <% form_for(@tag) do |f| %>
---
> <% form_for(@tags) do |f| %>
16c16
< <%= link_to 'Show', @tag %> |
---
> <%= link_to 'Show', @tags %> |
diff -r single/app/views/tags/index.html.erb plural/app/views/tags/index.html.erb
8c8
< <% for tag in @tags %>
---
> <% for tags in @tags %>
10,13c10,13
<     <td><%=h tag.name %></td>
<     <td><%= link_to 'Show', tag %></td>
<     <td><%= link_to 'Edit', edit_tag_path(tag) %></td>
<     <td><%= link_to 'Destroy', tag, :confirm => 'Are you sure?', :method => :delete %></td>
---
>     <td><%=h tags.name %></td>
>     <td><%= link_to 'Show', tags %></td>
>     <td><%= link_to 'Edit', edit_tags_path(tags) %></td>
>     <td><%= link_to 'Destroy', tags, :confirm => 'Are you sure?', :method => :delete %></td>
20c20
< <%= link_to 'New tag', new_tag_path %>
---
> <%= link_to 'New tags', new_tags_path %>
diff -r single/app/views/tags/new.html.erb plural/app/views/tags/new.html.erb
1c1
< <h1>New tag</h1>
---
> <h1>New tags</h1>
3c3
< <%= error_messages_for :tag %>
---
> <%= error_messages_for :tags %>
5c5
< <% form_for(@tag) do |f| %>
---
> <% form_for(@tags) do |f| %>
diff -r single/app/views/tags/show.html.erb plural/app/views/tags/show.html.erb
3c3
<   <%=h @tag.name %>
---
>   <%=h @tags.name %>
7c7
< <%= link_to 'Edit', edit_tag_path(@tag) %> |
---
> <%= link_to 'Edit', edit_tags_path(@tags) %> |

テストのほうも、複数形で作成したscaffoldは失敗する。

diff -r single/test/functional/tags_controller_test.rb plural/test/functional/tags_controller_test.rb
15,17c15,17
<   def test_should_create_tag
<     assert_difference('Tag.count') do
<       post :create, :tag => { }
---
>   def test_should_create_tags
>     assert_difference('Tags.count') do
>       post :create, :tags => { }
20c20
<     assert_redirected_to tag_path(assigns(:tag))
---
>     assert_redirected_to tags_path(assigns(:tags))
23c23
<   def test_should_show_tag
---
>   def test_should_show_tags
33,35c33,35
<   def test_should_update_tag
<     put :update, :id => tags(:one).id, :tag => { }
<     assert_redirected_to tag_path(assigns(:tag))
---
>   def test_should_update_tags
>     put :update, :id => tags(:one).id, :tags => { }
>     assert_redirected_to tags_path(assigns(:tags))
38,39c38,39
<   def test_should_destroy_tag
<     assert_difference('Tag.count', -1) do
---
>   def test_should_destroy_tags
>     assert_difference('Tags.count', -1) do

まとめ

  • script/generate scaffoldでは複数形は避けること。
  • コントローラを単数形にしたい場合は手動で作成すること。

ほんとかなあ・・・。