form_labels_and_errors ヘルパー : 小林研 Rails Tips (42)

はじめに

Rails Tips の 42 回目です。昨日、フォームのラベルとエラーを表示する form_label_and_error を紹介しました。今回は、ラベル行を作成する際に利用する form_labels_and_errors ヘルパーを紹介します。

form_label_and_error の使い方

昨日説明した form_label_and_error の実際の利用例を示します。これは、シラバスの編集画面では書籍を追加する場合のフォームです。各行にラベルの項目列とフォームの内容列があります。このラベルの描画部分に form_label_and_error を使っていました。キーでループすることでラベル部分が共通化できているだけでなく、text_field の入力部分も else で共通化できています。

= form_with model: @new_textbook, url: create_textbook_teacher_syllabus_syllabus_textbooks_path do |f|
  %table.bordered
    %thead
      %tr
        %th 項目
        %th 内容
    %tbody
      - add_texts = {yomi: "(ひらがなと全角スペースのみ)", isbn: "(検定教科書以外はハイフンなし13桁かハイフンあり17桁必須)"}
      - %i[name yomi author price kentei isbn publisher_id].each do |key|
        %tr
          %th= form_label_and_error f, @new_textbook, key, add_text: add_texts[key]
          %td
            - case key
            - when :price
              = f.number_field key
            - when :kentei
              = f.collection_radio_buttons :kentei, [%w[検定教科書 true], %w[通常書籍 false]], :last, :first
            - when :publisher_id
              = @new_textbook.publisher.name
            - else
              = f.text_field key, size: 20, autofocus: key == :name

form_labels_and_errors の使い方

一方で出版社のページでは書籍一覧のページの表の中で編集フォームが存在しています。この形もフォームも多いので、ヘッダ部分は form_label_and_error のみのループを実行する必要があります。同じようなループをあちこちに書くのは莫迦らしいので、専用の form_lables_and_errors というヘルパーを用意しました。key だった部分がキーの配列になり、add_text の部分が key で引くことができるハッシュになっているだけです。

= form_with model: [:teacher, @textbook] do |f|
  %table.bordered
    - keys = %i[name yomi author isbn kentei price]
    %thead
      = form_labels_and_errors f, @textbook, keys,
      add_texts: {yomi: "(ひらがなと全角スペースのみ)", isbn: "(検定教科書以外はハイフンなし13桁かハイフンあり17桁必須)"}
    %tbody
      %tr
        - keys.each do |key|
          %td
            - case key
            - when :kentei
              = f.collection_radio_buttons key, [%w[検定教科書 true], %w[通常書籍 false]], :last, :first
            - when :price
              = f.number_field key
            - else
              = f.text_field key, autofocus: key == :name
      %tr
        %td.warning{colspan: 6}
          = f.hidden_field :publisher_id
          = f.hidden_field :url
          = f.submit

form_labels_and_errors のテスト

テストは以下のようになります。ひとつのパターンしかテストしていませんが、form_label_and_error の方でさまざまなパターンをテストしているので問題ないでしょう。上の例では使っていませんが、labels と同様に add_control: true をつけることで制御列を追加できます。

      it "form_labels_and_errors で適切なラベルが複数設定できること" do
        form_with model: year do |f|
          expect(helper.form_labels_and_errors(f, year, %i[year default_year],
                                                add_texts: {
                                                  default_year: "add_text",
                                                },
                                                add_control: true)).to(
            eq [
                 "<tr><th><div>#{lbl}</div></th>",
                 "<th><div><label for=\"year_default_year\">デフォルト年度</label>",
                 "<span>add_text</span></div></th>",
                 "<th>制御</th></tr>"
               ].join("")
          )
        end
      end

form_labels_and_errors の実装

こちらは単に form_label_and_error をループしているだけなので、実装は難しくないと思います。

  # @param [form] form
  # @param [Object] model
  # @param [<Array<Symbol>] keys
  # @param [Hash] add_texts
  def form_labels_and_errors(form, model, keys, add_texts: {}, add_control: false)
    content_tag :tr do
      keys.each do |key|
        concat content_tag(:th) { form_label_and_error(form, model, key, add_text: add_texts[key]) }
      end
      if add_control
        concat content_tag(:th) { I18n.t "common.control" }
      end
    end
  end

おわりに

form_label_and_errorform_labels_and_errors を利用することで、縦型・横型のどちらの form でもラベル描画が楽に記述できるようになりました。