1 つのフォームで複数のモデルオブジェクトを使う

1 つのフォームで複数のモデルオブジェクトを使いたい場合、Merb では「text_field :mail」のようには書けず、「text_field :name=>"user[#{user.id}][mail]"」のように手動で name 属性を指定しないといけない。

たとえば:

<% @users.each do |user| %>
<%   id = user.id %>
<p>
  <%= text_field :name => "user[#{user.id}][name]", :label => 'Name',
                 :value => user.name, :id => "user_name_#{user.id}", %>
  <%= text_field :name => "user[#{id}][mail]", :label => 'Mail',
                 :value => user.mail,  :id => "user_mail_#{user.id}" %>
</p>
<% end %>

とすると:

<p>
  <label for="user_name_101">Name</label>
  <input type="text" name="user[101][name]" id="user_name_101" value="Foo" />
  <label for="user_mail_101">Mail</label>
  <input type="text" name="user[101][mail]" id="user_mail_101" value="foo@mail.com" />
</p>
<p>
  <label for="user_name_102">Name</label>
  <input type="text" name="user[102][name]" id="user_name_102" value="Bar" />
  <label for="user_mail_102">Mail</label>
  <input type="text" name="user[102][mail]" id="user_mail_102" value="bar@mail.com" />
</p>

が生成される。こうすると、コントローラ側では:

def update
  p params[:user] #=> {"101"=>{"name"=>"Foo", "mail"=>"foo@mail.com"},
                  #    "102"=>{"name"=>"Bar", "mail"=>"bar@mail.com"}}
  params[:user].each do |user_id, values|
    user = User.get(user_id)  or raise NotFound
    user.update_attributes(values)
    ...
  end
end

のように書ける。
しか〜し、手動で name 属性や id 属性を指定するのはいけてない。

そこで、merb-helpers を拡張してみた。これを「require 'merb-helpers-monkey'」とかして読み込むと:

<% @users.each do |user| %>
<%=  fields_for user, :index_by => :id do %>
<p>
  <%= text_field :name, :label => 'Name' %>
  <%= text_field :mail, :label => 'Mail' %>
</p>
<%   end =%>
<% end %>

のように書ける。ステキ!
ポイントは、fields_for につけた「:index_by => :id」ね。分かってるとは思うけど。