idと名前を関連付けるformのselectはcollection_selectメソッドを使おう

collection_selectメソッドの存在を見落としていてselectメソッドで名前を取得して、controllerでそれを対応するidに変換してModel#saveしてしまっていた。

そんなときはcollection_selectメソッドがめちゃ便利なので使い方メモっておく。

定義部分はactionview/lib/action_view/helpers/form_options_helper.rbにあり、以下のようになってる。

  # Wraps ActionView::Helpers::FormOptionsHelper#collection_select for form builders:
      #
      #   <%= form_for @post do |f| %>
      #     <%= f.collection_select :person_id, Author.all, :id, :name_with_initial, prompt: true %>
      #     <%= f.submit %>
      #   <% end %>
      #
      # Please refer to the documentation of the base helper for details.
      def collection_select(method, collection, value_method, text_method, options = {}, html_options = {})
        @template.collection_select(@object_name, method, collection, value_method, text_method, objectify_options(options), @default_options.merge(html_options))
      end

methodっていうのがselectタグのname属性になる。

collectionってのは対応するモデルのデータ.allでokだけど、viewでActiveRecordメソッド使うのっておかしい感じするのでcontrollerかmodelで必要なものを取り出してインスタンス変数で渡すのがよい気がしたんだけど、それだとvalidationで例外が発生した時にsaveが完了しなかったときの処理(同じページのレンダリングとか)までたどり着いてくれない...いい方法あれば知りたい。

そしてvalue_methodが実際にhtmlタグ内でvalueのとこに格納される値、text_methodがoptionタグの中に挿入される値って感じになる。

まとめると、

<%= f.collection_select :person_id, Author.all, :id, :name_with_initial, prompt: true %>

は、下のように変換される。

<select name="person_id">
  <option value="id">name_with_initial</option>
  ...
</select>

便利ですなー