Rails のフォームで配列形式のデータを扱う方法
「今週の話題」を除くと、すごく久しぶりの投稿になってしまいました。いろいろあってなかなか時間が取れなかったのですが、やっと復活できそうです。ネタはだいぶ溜まっているので、頑張って書いていこうと思います。
さて、本日ご紹介するのは Rails アプリケーションで配列構造のデータを扱うフォームに役立つ Tips です。 Rails は特殊な name 属性を指定することでフォームの送信データを自動的にハッシュに直してくれるのは皆さんご存知かと思います。しかし、実は同様にして配列を生成させる指定も存在しています。私の知る限りリファレンスには明確な説明がないのですが、ハッシュに配列を含めたり、逆にハッシュの配列を作ったりすることも可能で、なかなか便利です。
今やっている仕事でけっこう活用していて、利用方法や問題点などもわかってきたので、そのあたりも含めて記事にしてみようと思います。ご存じなかった方は、ぜひ読んでみてください。
ハッシュ構造を扱うフォーム
まずは基本から。 Rails では、各フィールドの name 属性を "post[title]" のようにすることで、フォームデータにハッシュ構造を持たせることができます。例えば以下のフォームを送信すると…
<% form_tag :action => :create do -%> <%= text_field_tag 'post[title]' %><br /> <%= text_area_tag 'post[body]' %><br /> <%= submit_tag %> <% end -%>
コントローラに渡される params は以下のようになります(id などは省略)。
params = { :post => { :title => "タイトル", :body => "本文" } }
この機能は Scaffold でも使われていますし、 ActionController のリファレンスにも掲載されているので、ご存知の方も多いでしょう。
配列構造を扱うフォーム
それでは、ハッシュではなく配列を生成するにはどうすればよいのでしょうか。答えは簡単。空の大括弧 '[]' を使います。 text_field ヘルパーに対する同様の指定( ActiveRecord オブジェクトの id が自動的に埋め込まれ、結果的にハッシュになる)とは別物なので注意してください。
<% form_tag :action => :edit_tag, :id => @id do -%> Tag1 : <%= text_field_tag 'tags[]' %><br /> Tag2 : <%= text_field_tag 'tags[]' %><br /> Tag3 : <%= text_field_tag 'tags[]' %><br /> Tag4 : <%= text_field_tag 'tags[]' %><br /> <%= submit_tag %> <% end -%>
これを送信すると、 params には以下のように配列構造で格納されます。
params = { :tags => ['tag1', 'tag2', 'tag3', 'tag4'] }
ハッシュと併用することもできます。
<% form_tag :action => :create do -%> <%= text_field_tag 'post[title]' %><br /> <%= text_area_tag 'post[body]' %><br /> Tag1 : <%= text_field_tag 'post[tags][]' %><br /> Tag2 : <%= text_field_tag 'post[tags][]' %><br /> Tag3 : <%= text_field_tag 'post[tags][]' %><br /> Tag4 : <%= text_field_tag 'post[tags][]' %><br /> <%= submit_tag %> <% end -%>
ハッシュの配列も作れます。
<% form_tag :action => :rename_all do -%> <% @posts.each do |post| -%> <%= hidden_field_tag 'posts[][id]', post.id %> <%= text_field_tag 'posts[][title]', post.title %><br /> <% end -%> <%= submit_tag %> <% end -%>
それぞれがどんな params に変換されるかは、だいたいわかりますよね。これらを活用すれば、複数のレコードを一括編集するフォームが楽に作れます。同じフィールドを単純に並べるだけで配列になるので、 JavaScript による動的な項目追加も簡単です。
<script type="text/javascript"> function addTagField() { $('tags').insert($('tag_field').innerHTML); } </script> <% form_tag :action => :create do -%> <%= text_field_tag 'post[title]' %><br /> <%= text_area_tag 'post[body]' %><br /> Tags <div id="tags"></div> <%= button_to_function 'タグを追加', 'addTagField();' %><br /> <%= submit_tag %> <% end -%> <div id="tag_field" style="display:none;"> <%= text_field_tag 'post[tags][]' %><br /> </div>
また、表示順序がそのまま配列要素の順序になるという利点もあります。この点を活用して、 acts_as_list なテーブルをドラッグ&ドロップで並べ替えられるフォームを作ってみるのも面白いかと思います。というか、仕事で実際に作りました(笑)。
チェックボックスへの対応
テキストフィールドだけのフォームであれば以上で問題ないのですが、チェックボックスが加わると話が多少複雑になります。とくに以下のように各項目の先頭にチェックボックスがある場合には、問題が発生します。
<% form_tag :action => :delete do -%> <% @posts.each do |post| -%> <%= check_box_tag 'posts[][flag]' %> <%= hidden_field_tag 'posts[][id]', post.id %> <%= post.title %><br /> <% end -%> <%= submit_tag %> <% end -%>
例えば 2 つのポストがあり、 2 番目のみがチェックされた状態で送信されたとすると、 QueryString は以下のようになります。チェックボックスはチェックが外れているとフィールド自体が送信されないことに注意してください。
posts[id]=0&posts[flag]=1&posts[id]=1
これでは、 Rails は "post[flag]=1" がどちらの要素に属するのか判断できません。結果的に 1 番目のポストがチェックされたと誤認して、以下のような params を生成してしまいます。
params = { posts = [ { :id => "0", :flag => "1" }, { :id => "1" } ] }
この問題を解消するには、以下のように hidden フィールドを先に持ってきます。
<% form_tag :action => :delete do -%> <% @posts.each do |post| -%> <%= hidden_field_tag 'posts[][id]', post.id %> <%= check_box_tag 'posts[][flag]' %> <%= post.title %><br /> <% end -%> <%= submit_tag %> <% end -%>
Rails は同じ名前のフィールドが現れたときに新しい要素に移ったと解釈するので、常にフィールドが送信されるテキストフィールドや hidden フィールドを先頭に持ってくれば、上述のような混乱が起きないわけです。適当なフィールドがなければ、ダミーの hidden フィールドを追加してしまえば良いでしょう。
ラジオボタンへの対応
さらに厄介なのがラジオボタンです。ラジオボタンは name フィールドでグルーピングされるので、以下のようにラジオボタンを記述すると、すべてのポストを通してひとつのボタンしか ON にできないという憂き目にあいます。
<% form_tag :action => :publish do -%> <% @posts.each do |post| -%> <%= hidden_field_tag 'post[][id]', post.id %> <%= post.title %> <%= radio_button_tag 'post[][publish]', '1' %>公開 <%= radio_button_tag 'post[][publish]', '0' %>非公開<br /> <% end -%> <%= submit_tag %> <% end -%>
残念ながらこればっかりは HTML の仕様なので、良い解決方法が見いだせていません。いまのところ、以下のようにして項目ごとに name を変更し、コントローラ側で頑張って解析しています。
<% form_tag :action => :publish do -%> <% @posts.each_with_index do |post, index| -%> <%= hidden_field_tag 'post[][id]', post.id, :id => nil %> <%= post.title %> <%= radio_button_tag "post[][publish#{index}]", '1' %>公開 <%= radio_button_tag "post[][publish#{index}]", '0' %>非公開<br /> <% end -%> <%= submit_tag %> <% end -%>
しかし、これだと配列構造のメリットが台無しですね。なにかうまい方法があるといいのですが・・・。
その他の注意点
記事の内容とは直接関係ないのでスルーしてきたのですが、実はここまでのサンプルにはひとつ問題があります。 text_field_tag などのヘルパーは name 属性をもとにして id 属性を自動生成するので、上記のように name が同一のフィールドを複数生成すると、 id 属性が重複してしまうのです。まあ、実際に問題が顕在化することは少ないと思いますが、念のため ":id ⇒ nil" オプションを指定して id の生成を抑制したほうが良いでしょう。
以上、本日は Rails のフォームで配列構造を自動生成させる方法をご紹介しました。とくに業務システムなどでは Excel っぽいグリッド形式のフォームを作ることが多いので、上記の機能はかなり役に立つのではないでしょうか。ぜひ活用してください!
詳しくはこちらの記事をどうぞ!
この記事にコメントする