LoginSignup
7
12

More than 5 years have passed since last update.

ActiveRecordを使わない動的なフォームとバリデーション

Last updated at Posted at 2017-11-23

はじめに

Railsでデータベース(ActiveRecord)を使用しない可変部分を含む動的なFormを作成します。
ActiveModelを使用してFormと紐付かせると、ActiveRecordを使用した場合と同様にValidationを使用することができます。
今回、フォームの一部の項目が可変なパターンが探してもなかったので、そのやり方をまとめておきました。

GitHubで作ったものを公開しています。
https://github.com/FukushimaTakeshi/send_form

環境

Ruby : 2.4.2
Ruby on Rails : 5.1.4

作ったもの

scaffoldベースの簡単な問い合わせフォームです。このフォームの備考欄をテーブルのデータ数にあわせて、動的に表示します。

初期表示

2017-11-20_00h31_36.png

バリデーションエラー

Railsではデフォルトでバリデーションエラー時にラベルやフォームの要素をfield_with_errorsというclassのdivで囲んでくれるので、cssなどでそのclassを指定しておけばエラーが発生した項目に色を付けられます。
2017-11-20_00h40_24.png

可変部分のデータ

この場合、3件の備考欄を表示できるようにします。

sqlite> select * from free_forms;
1|備考1|2017-11-19 12:03:06.254618|2017-11-19 12:03:06.254618
2|備考2|2017-11-19 12:03:06.303466|2017-11-19 12:03:06.303466
3|備考3|2017-11-19 12:03:06.393128|2017-11-19 12:03:06.393128
sqlite>

Model

ActiveModel::ModelをincludeすることでActiveRecordと同じような感じでバリデーションが定義できます。
ポイントはレコード数によって変動する備考欄のところをdefine_free_text_method内でclass_evalを使用してアクセサとバリデーションを定義しているところです。バリデーションエラーで色を付けるためにはここを通さないとダメっぽいので、アクセサの定義は必要です。
I18n.backend.store_translationsではテーブルの値に基づき新しい翻訳文を追加しています。

models/inquiry.rb
class Inquiry
  include ActiveModel::Model

  attr_accessor :name, :tel, :email

  validates :name, presence: true, length: { maximum: 20 }
  validates :tel, presence: true, numericality: true, length: { maximum: 15 }
  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
  validates :email, presence: true, length: { maximum: 255 },
                    format: { with: VALID_EMAIL_REGEX }

  def initialize(free_form, params={})
    define_free_text_method(free_form)
    super(params)
  end

  def save!
  end

  private

  def define_free_text_method(free_form)
    free_form.each_with_index do |value, index|
      class_eval do
        attr_accessor :"free_text_#{index}"
        validates :"free_text_#{index}", length: { maximum: 100 }
      end
      # I18n ja.ymlのfree_formの数に応じた項目を定義(ex. free_text_0: '備考1')
      I18n.backend.store_translations :ja, activemodel: {
        attributes: { 'inquiry': { "free_text_#{index}": value[:remark] } }
      }
    end
  end
end

Controller

一応、入力→確認→送信 の問い合わせフォームのつもりですが、newとconfirmのバリデーション部分までで他は仮で作っています。
inquiry_paramsで備考欄の数に応じてStrong Parameterに追加するようにします。

controllers/inquiry_controller.rb
class InquiryController < ApplicationController

  def new
    @free_form = FreeForm.all
    @inquiry = Inquiry.new(@free_form)
  end

  def confirm
    @free_form = FreeForm.all
    @inquiry = Inquiry.new(@free_form, inquiry_params(@free_form.count))
    render :new unless @inquiry.valid?
  end

  def create
    @free_form = FreeForm.all
    @inquiry = Inquiry.new(@free_form, inquiry_params(@free_form.count))
    @inquiry.save!
  end

  private

  # Strong Parameters
  def inquiry_params(count)
    free_texts_params = count.times.map { |index| "free_text_#{index}".to_sym }
    params.require(:inquiry).permit([:name, :tel, :email] << free_texts_params)
  end
end

View

Modelに紐付いたフォームのため、備考欄も含めてform_forで定義しています。(※rails5.1からは#form_withが使えるようです。)
confirmとcreateのViewは省略。

views/inquiry/new.html.erb
<%= form_for @inquiry, url: inquiry_confirm_path do |f| %>
  <div>
    <h1>お問い合わせ</h1>
  </div>

  <% if @inquiry.errors.any? %>
    <div>
      <strong>入力内容にエラーがあります</strong>
      <ul>
        <% @inquiry.errors.full_messages.each do |msg| %>
          <li><%= msg %></li>
        <% end %>
      </ul>
    </div>
  <% end %>

  <table>
    <tr>
      <th><%= f.label :name %></th>
      <td><%= f.text_field :name %></td>
    </tr>
    <tr>
      <th><%= f.label :tel %></th>
      <td><%= f.text_field :tel %></td>
    </tr>
    <tr>
      <th><%= f.label :email %></th>
      <td><%= f.text_field :email %></td>
    </tr>
    <% @free_form.present? %>
      <% @free_form.each_with_index do |value, index| %>
        <tr>
          <th><%= f.label :"free_text_#{index}" %></th>
          <td><%= f.text_area :"free_text_#{index}", size: "50x5" %></td>
        </tr>
      <% end %>
  </table>
  <%= f.submit '確認' %>
<% end %>

i18n

i18nで多言語化にも対応可能です。2行目に記載しているようにactivemodelと定義します。

ja.yml
ja:
  activemodel:
    models:
      inquiry:
    attributes:
      inquiry:
        name: 名前
        tel: 電話番号
        email: メールアドレス

    errors:
      format: '%{attribute} %{message}'
      messages:
        blank: を入力して下さい。
        too_long: は%{count}文字以内で入力して下さい。
        not_a_number: は半角数字で入力して下さい。
        invalid: は不正な値です。
7
12
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
12