フォームで選択肢を実装した場合、その他項目を設けてその他が選択された時はテキスト入力を必須とする実装をしたい時がある。
割とどこにでもある実装だが、バリデーションも含めてちゃんと実装しようとすると、「あれ、どうやるんだっけ?」となったのでメモとして残す。
概要
例えば会員登録するユーザーの職業情報を得るために、いくつか選択肢を用意し、選択肢に当てはまらない場合はフリーテキスト入力を必須となるようにしたい。
その場合、フリーテキスト欄をその他が選択された場合だけ表示、それ以外の場合は非表示という実装をJavaScriptで行う以外に、Modelのvalidationにも記述する必要がある。
選択できる職業は、エンジニア、マーケッター、デザイナー、オペレーション、マネジメント、その他の6種類とし、その他が選択された時だけフリーテキスト欄入力を必須とする。
Schema
テーブルのカラムには、下記の2つのカラムをつける。(カラムの追加方法などはここでは省略)
db/schema.rb
create_table "users", force: :cascade do |t|
t.integer "job", default: 0, null: false, comment: "職種"
t.string "other_jobs", comment: "その他の職種"
end
jobs
はenum管理するのでintegerで登録する。
Form
次にformに入力項目を追加する。(form_withの引数は適当)
views/users/new.html.slim
= form_with(model: user, url: registration_path, method: :post) do |form|
= form.label :job
= form.select :job, User.jobs_i18n.invert, {}, required: true
= form.text_field :other_jobs, required: true, class: 'other_jobs-input', placeholder: '職種を入力してください'
User.jobs_i18n.invert
は選択肢としてjobsのenumを一覧にして日本語化し、かつenumのhashのkey, valueを逆転させる。
これで選択肢の表示名にenumのkeyの日本語名を、valueにkeyをセット出来る。
JQuery
Formの表示・非表示を動的に行うためJQueryで操作を行う。
$(function() {
$(".other_jobs-input").hide();
});
// 選択肢の変更を検知してイベントを発生させる。
$('select[name="user[job]"]').change(function() {
if ($(this).val() === 'others')
$(".other_jobs-input").show();
else
$(".other_jobs-input").hide();
});
Model
最後にModelのコードを。
class User < ApplicationRecord
validates :other_jobs, presence: true, if: -> { self.others? }
validates_presence_of %i(
job
)
before_validation :other_jobs_validation
enum job: {
engineer: 0,
marketter: 1,
designer: 2,
operation: 3,
management: 4,
others: 5
}
private
def other_jobs_validation
self.other_jobs = "" unless self.others?
end
まずenumを用いて選択肢管理を行う。
job
は必須項目としたいのでvalidates_presence_of
を使いvalidationをかける。
また、job
でothers
が選択された時はother_jobs
を必須としたいので、if:
オプションにlambdaを使ってbooleanを返すブロックを渡す。
こうすることで、職業選択欄でその他が選択された時にテキスト入力を必須と出来る。
逆にその他以外が選択された時はother_jobs
に値が入っていて欲しくない。
そこでother_jobs_validation
を定義し、before_validation
に渡すことで目的を果たすことが出来る。