form_forでフォームを書く
久しぶりにRailsを触るぜ!
いやー, とある時期にRailsは触っていたけども, 最近めっきり触っていないので, Railsの進化にとまどっている次第です. なんせ触っていたのはバージョン1.2.3のだしね. 今はもう2.2.2…… とまどうのは仕方ないけど, ちょいと使ってみたかったので, リハビリも兼ねて追いかけるコトに. そんでココにメモっとく.
ひとまず本は使わず(というか買わず), ソースとWebでやっていこうと思う. まずは早速つまずいたフォームの書き方.
form_forヘルパー
Rails1.*のときはstart_form_tag, end_form_tag(form_tag)を使っていたけど, Rails2.0からform_forというヘルパーを使うようになったらしい. それに合わせて, text_fieldなどの記述方法も変更. 以下のようになる.
<% form_for :human, :url => {:action => 'create'} do |f| -%> <p> <label for="human_name">Name</label>: <%= f.text_field 'name' %> </p> <p> <label for="human_age">Age</label>: <%= f.text_field 'age' %> </p> <%= f.submit 'Save' %> <% end -%>
form_forによって(X)HTMLの階層とRubyのブロックによるインデントがある程度一致してわかりやすい感じ. 他にもfields_forなどがある.
あと以前は上記の場合必ず@human変数にモデルのインスタンスを入れておく必要があったけど, form_forでは第2引数にモデルのインスタンスを与えればいい(その場合オプションは第3引数)ので, 必ずしも@humanという名前の変数である必要はないし, もっと言えば上記のように省略できる = newのときにモデルのインスタンスを生成する必要がない.
ちなみにform_forに渡しているブロックのパラメータであるfにはFormBuilderというクラスのインスタンスが渡される模様. というコトで次はFormBuilder.
FormBuilder
コレは素敵. 惚れた. コレを使うとf.text_fieldなどの挙動を変えらるとか.
仕組みは単純で, まず下記のようにFormBuilderのサブクラスを作る.
# app/helpers/extended_form_builder.rb class ExtendedFormBuilder < ActionView::Helpers::FormBuilder def text_field(field, *args) (args[0] && args[0][:label] ? "#{label(field, args[0].delete(:label))}: " : '') + super end end
使うときは下記のようにして指定すればOK.
<% form_for :human, :url => {:action => 'create'}, :builder => ExtendedFormBuilder do |f| -%> <%= f.text_field 'name', :label => 'Name' %> <% end -%>
こんな風に出ます(text_fieldのところだけ抜粋).
<label for="human_name">Name</label>: <input type="text" size="30" name="human[namae]" id="human_name"/>
ってな具合にフィールド周りのタグをFormBuilderの方に固めてしまうコトができるのかー.
実際には下記のように使ってみました(google:非同期アップロードなんてそう頻繁に使うのか, と言われればアレですが).
module ApplicationHelper def async_upload_form_for(record_or_name_or_array, *args, &proc) args << {} unless args[-1] && args[-1].class == Hash html_options = { :target => AsyncUploadFormBuilder.target(record_or_name_or_array), :multipart => true } options = args.pop options[:html] = (options[:html] || {}).update html_options options[:builder] = AsyncUploadFormBuilder form_for record_or_name_or_array, *(args << options), &proc end end
class AsyncUploadFormBuilder < ActionView::Helpers::FormBuilder def self.target(form_name) "#{form_name}_async_upload" end def file_field(field, *args) target = self.class.target(@object_name) options = { :id => target, :name => target, :style => 'width: 0; height: 0; position: absolute; left: -9999px;' } @template.content_tag(:iframe, '', options) + super end end
enctype属性やtarget属性を勝手に補完して, file_fieldを呼び出したときにiframeタグも出力する, ってな感じです. 後はviewに下記のような感じで書くだけ.
<% async_upload_form_for :human, :url => {:action => 'upload'} do |f| -%> <%= f.file_field 'file' %> <%= f.submit 'Upload' %> <% end -%>
すっきり!