Rails

Railsにて、複数あるチェックボックスが1つも選択されていなければエラーになるバリデーションを作る

More than 5 years have passed since last update.

環境

  • Ruby 1.9.2p320
  • Ruby on Rails 3.2.3

要件

例えばウェブサイトに以下のような入力フォームがあるとします。

  • あなたの好みを教えてください。(必須項目)
    • ゾンビ
    • 幽霊
    • ナイアーラトテップ

この3つの選択肢はチェックボックスで複数選択できるようにします。また、1つも選択されなかったときはエラーになるようにします。

方法

以下のようなテーブルを定義します。

class CreateUsers < ActiveRecord::Migration
  def change
    create_table :users do |t|
      t.boolean :like_ghost
      t.boolean :like_nyarlathotep
      t.boolean :like_zombie
      t.timestamps
    end
  end
end

次に以下のようなビューを用意します。

p あなたの好みを教えてください。(必須項目)
= form_for(@user) do |f|
  = hidden_field_tag 'user[like]'
  = f.check_box :like_zombie
  | ゾンビ
  = f.check_box :like_ghost
  | 幽霊
  = f.check_box :like_nyarlathotep
  | ナイアーラトテップ
  = f.submit '保存'

バリデーションをするために hidden_field_tag メソッドを使用して like というフィールドを用意しています。

次に以下のようなモデルを用意します。

# coding: utf-8

class User < ActiveRecord::Base
  attr_accessor :like
  attr_accessible :like, :like_ghost, :like_nyarlathotep, :like_zombie

  validates :like, present_like: true
end

like はデータベースのフィールドとして存在していないため、attr_accessor メソッドで読み書きできるようにする必要があります。そして、like_* が1つも選択されなかったときにバリデーションエラーにするため、 present_like という独自のバリデーションを作成します。

# coding: utf-8

class PresentLikeValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    like_name = ['ghost', 'nyarlathotep', 'zombie']
    like_values = like_name.map { |name| record.__send__("like_#{name}") }

    unless like_values.include?(true)
      record.errors[attribute] << (options[:message] || 'を選択してください。')
    end
  end
end

validate_each メソッドに渡される3つの引数にはそれぞれ以下のようなデータが渡されます。

  • record: #<User id: nil, like_ghost: false, like_nyarlathotep: false, like_zombie: false, created_at: nil, updated_at: nil>
  • attribute: :like
  • value: ""

like_name.map { |name| record.__send__("like_#{name}") } でレコードに入っている各 like_* の値が入った配列を生成し、include? メソッドを使用して1つでも true が入っているか (チェックボックスが選択されているか) どうかを判別しています。

record.errors[attribute] << (options[:message] || 'を選択してください。')options には、モデル内で

validates :like, present_like: { message: 'を選択してね' }

などとしたときに、{ message: 'を選択してね' } が格納されます。ちなみに前の例では :present_liketrue を指定していましたが、 false などを指定しても問題なく動作します。

この独自バリデーションのコードを present_like_validator.rb という名前で保存します。_validator をファイル名の最後に付けないといけないようです。

以上です。もっと良い方法があったら教えてもらえると嬉しいです。