form_forブロックの中のrender :partialの悩み

重複のあるnewとedit

  • 通常、form_forを使ってnew、editを書くと以下のような状況になるかと...。(中身がとても似ている。)
# app/views/slips/new.rhtml
<% form_for :slip, :url=>{:action=>'create'} do |f| %>
  <%= f.text_field :number  %>
  <%= f.text_field :executed_on  %>
  <%= f.text_field :total_yen  %>
  <%= submit_tag 'Create' %>
<% end %>


# app/views/slips/edit.rhtml
<% form_for :slip, :url=>{:action=>'update', :id => @slip} do |f| %>
  <%= f.text_field :number  %>
  <%= f.text_field :executed_on  %>
  <%= f.text_field :total_yen  %>
  <%= submit_tag 'Update' %>
<% end %>

scaffoldに倣う(Rails1.2.6までの)

  • だからRails1.2.6までのscaffoldに倣い、重複を嫌って共通フォームを_form.rhtmlに抜き出して...
  • form_forブロックのブロック変数fを:localsオプションで_form.rhtmlに渡して描画していた。(ブロック変数であるローカル変数fは、:localsオプションで引き継がないとf.text_fieldがnil.text_fieldになってしまい、エラーが発生してしまう。)
  • でも、locals=>{:f=>f}って目障り。(シンプルに書けるform_forの魅力を打ち消している気がする。)
# app/views/slips/_form.rhtml
<%= f.text_field :number  %>
<%= f.text_field :executed_on  %>
<%= f.text_field :total_yen  %>


# app/views/slips/new.rhtml
<% form_for :slip, :url=>{:action=>'create'} do |f| %>
  <%= render :partial=>'form', :locals=>{:f=>f}  %>
  <%= submit_tag 'Create' %>
<% end %>


# app/views/slips/edit.rhtml
<% form_for :slip, :url=>{:action=>'update', :id => @slip} do |f| %>
  <%= render :partial=>'form', :locals=>{:f=>f}  %>
  <%= submit_tag 'Update' %>
<% end %>
      • ちなみにfは重複しなければ任意の変数名でOKのはず。(例: slip、slip_formなど。formではrender :partialが自動設定してくれるformと重なるので、後々問題になるかもしれない。)

発想の転換インスタンス変数に)

  • その後、インスタンス変数はビュー共通の変数になる、ということを思い出してブロック変数を@fにしてやってみる...
# app/views/slips/_form.rhtml
<%= @f.text_field :number  %>
<%= @f.text_field :executed_on  %>
<%= @f.text_field :total_yen  %>


# app/views/slips/new.rhtml
<% form_for :slip, :url=>{:action=>'create'} do |@f| %>
  <%= render :partial=>'form'  %>
  <%= submit_tag 'Create' %>
<% end %>


# app/views/slips/edit.rhtml
<% form_for :slip, :url=>{:action=>'update', :id => @slip} do |@f| %>
  <%= render :partial=>'form'  %>
  <%= submit_tag 'Update' %>
<% end %>
  • 予想外にちゃんと動いた...(シンプルさの面からは好き)
  • しかし、邪道な気がする。今までブロック変数をインスタンス変数にしている例を見たことがない...。(誰かに怒られそうな気がする。良いのだろうか?)

再びscaffoldに頼る(Rails2.0.2の)

  • Railsを2.0.2にバージョンアップして、form_forオンリーになったscaffoldに期待して調べてみる。(pタグ、bタグもそのまま掲載。)
  • しかし、残念ながら重複のある状態のままだった...。(_form.rhtmlは生成されなくなっている。あれ、DRYの原則は?お手本を示して欲しかった...。)
# app/views/slips/new.html.erb
<h1>New slip</h1>
<%= error_messages_for :slip %>
<% form_for(@slip) do |f| %>
  <p>
    <b>Number</b><br />
    <%= f.text_field :number %>
  </p>
  <p>
    <b>Executed on</b><br />
    <%= f.text_field :executed_on %>
  </p>
  <p>
    <b>Total yen</b><br />
    <%= f.text_field :total_yen %>
  </p>
  <p>
    <%= f.submit "Create" %>
  </p>
<% end %>
<%= link_to 'Back', slips_path %>


# app/views/slips/edit.html.erb
<h1>Editing slip</h1>
<%= error_messages_for :slip %>
<% form_for(@slip) do |f| %>
  <p>
    <b>Number</b><br />
    <%= f.text_field :number %>
  </p>
  <p>
    <b>Executed on</b><br />
    <%= f.text_field :executed_on %>
  </p>
  <p>
    <b>Total yen</b><br />
    <%= f.text_field :total_yen %>
  </p>
  <p>
    <%= f.submit "Update" %>
  </p>
<% end %>
<%= link_to 'Show', @slip %> |
<%= link_to 'Back', slips_path %>

新たな発見!

  • 上記では期待外れだったが、新たな発見もあった。Rails2.0.2では、form_forブロック内がf.submit以外全く同じということに気付く。それなら、以下のようにも書ける。
      • f.submitという書き方は、Rails2.0から可能になったようだ。その他にもソースコードを確認すると、f.label(labelヘルパが追加された)、f.error_message_on、f.error_messages(error_messages_forの呼び出し)も可能になったようだ。
# app/views/slips/_form.html.erb
<%= error_messages_for :slip %>
<% form_for(@slip) do |f| %>
  <p>
    <b>Number</b><br />
    <%= f.text_field :number %>
  </p>
  <p>
    <b>Executed on</b><br />
    <%= f.text_field :executed_on %>
  </p>
  <p>
    <b>Total yen</b><br />
    <%= f.text_field :total_yen %>
  </p>
  <p>
    <%= f.submit button_value %>
  </p>
<% end %>


# app/views/slips/new.html.erb
<h1>New slip</h1>
<%= render :partial=>'form', :locals=>{:button_value=>'Create'} %>
<%= link_to 'Back', slips_path %>


# app/views/slips/edit.html.erb
<h1>Editing slip</h1>
<%= render :partial=>'form', :locals=>{:button_value=>'Update'} %>
<%= link_to 'Show', @slip %> |
<%= link_to 'Back', slips_path %>
  • ブロック変数の引き継ぎ問題は解決したが、その代わりに送信ボタンの値の設定が必要になった。
  • でも、ブロックが一つのファイルになり、コードの見通しが良くなった所は気に入っている。
  • new、editもシンプルになり、入力フォームと、それ以外の部分が明確になったところも気に入っている。


form_forを使ったform.rhtml、new.rhtml、edit.rhtmlはどう書くべきか、現在も悩み中。お手本、検索中です。