おぴよの気まぐれ日記

おぴよの気まぐれ日記

岡山やプログラミング、ファッションのこと、子育てや人生、生き方についての備忘録。

【Rails】バリデーションをまとめてみる

データを登録する際に利用するフォームですが、条件に応じて必須だったり制限したりする仕組みを「バリデーション」といいます。 Ruby on Railsでは以下のような感じで設定することができます。

class Hoge < ApplicationRecord
  validates :name, presence: true
end
# validationがない場合
> hoge = Hoge.new
=> #<Hoge:0x00007f803eabca78 id: nil, name: "", age: 0, gender: "man", birthday: Mon, 01 Jan 1900, created_at: nil, updated_at: nil>
> hoge.save
   (0.3ms)  BEGIN
  Hoge Create (2.8ms)  INSERT INTO "hoges" ("created_at", "updated_at") VALUES ($1, $2) RETURNING "id"  [["created_at", "2019-09-24 11:19:16.757343"], ["updated_at", "2019-09-24 11:19:16.757343"]]
   (0.8ms)  COMMIT
=> true

# validationがある場合
> hoge = Hoge.new
=> #<Hoge:0x00007fc554e8fd48 id: nil, name: "", age: 0, gender: "man", birthday: Mon, 01 Jan 1900, created_at: nil, updated_at: nil>
[2] pry(main)> hoge.save
   (0.3ms)  BEGIN
   (0.3ms)  ROLLBACK
=> false
[3] pry(main)> hoge.errors.full_messages
=> ["Name入力してください。"]

このようにvalidationを設定することで、条件が満たされていないとデータが保存されないように出来ます。

今回は入力が必須の設定であるpresenceを使ったので、最後のエラー文に"Name入力してください。"といったエラーが自動的に設定されます。 これらの設定が色々とありますので、そちらをまとめていきたいと思います!

今回使うテーブルはこちら↓↓

                                         Table "public.hoges"
   Column    |            Type             | Collation | Nullable |              Default              
-------------+-----------------------------+-----------+----------+-----------------------------------
 id          | bigint                      |           | not null | nextval('hoges_id_seq'::regclass)
 name        | character varying           |           | not null | ''::character varying
 age         | integer                     |           | not null | 0
 gender      | character varying           |           | not null | 'man'::character varying
 birthday    | date                        |           | not null | '1900-01-01'::date
 email       | character varying           |           | not null | 
 postal_code | character varying           |           | not null | 
 created_at  | timestamp without time zone |           | not null | 
 updated_at  | timestamp without time zone |           | not null | 
Indexes:
    "hoges_pkey" PRIMARY KEY, btree (id)

バリデーションが実行されるタイミング

バリデーションが実行されるタイミングは色々とあるのですが、例えばこの二つです!

  • hoge.save
  • hoge.valid?

saveはその名の通りデータを保存する際に使い、valid?は保存せずにvalidationが成功するかどうかを判断する時に使います。

また、save(validate: false)とするとvalidationしないようにすることも可能です。

他のものは、また別の機会でまとめてみたいと思います。

バリデーションのエラー

バリデーションに失敗すると勝手にエラーに関する情報が保存されます。

> hoge.errors
=> #<ActiveModel::Errors:0x00007fe8e626a940
 @base=#<Hoge:0x00007fe8ca962638 id: nil, name: "ai", age: 0, gender: "man", birthday: Mon, 01 Jan 1900, created_at: nil, updated_at: nil>,
 @details={:name=>[{:error=>:too_short, :count=>10}]},
 @messages={:name=>["は10文字以上で入力してください。"]}>

> hoge.errors.full_messages
=> ["Nameは10文字以上で入力してください。"]

errorsとすると、モデルの詳細な情報が取得できて、errors.full_messagesとするとエラーメッセージだけ取得できます。 使われ方としては、このfull_messagesを画面側で表示させてエラー内容を表示させたりします。

ここでNameという形で英語で表示されてしまってますが、これもi18nという仕組みを使うことで日本語表示させることも可能です。 が、ここでは説明しません。別の機会に使い方をまとめたいと思います。

バリデーションの種類

空かどうか(presence)

validates :name, presence: true

> hoge = Hoge.new
=> #<Hoge:0x00007fc556fe6618 id: nil, name: "", age: 0, gender: "man", birthday: Mon, 01 Jan 1900, created_at: nil, updated_at: nil>
> hoge.valid?
=> false

文字数(length)

# 最小の数
validates :name, length: { minimum: 10 }

# 最大の数
validates :name, length: { maximum: 20 }

# 数の範囲
validates :name, length: { in: 10..20 }

> hoge.name = 'ai'
=> "ai"
> hoge.name.length
=> 2
> hoge.valid?
hoge=> false
> hoge.errors.full_messages
=> ["Nameは10文字以上で入力してください。"

> hoge.name = 'aiueokakikukekosashisuseso'
=> "aiueokakikukekosashisuseso"
> hoge.name.length
=> 26
> hoge.valid?
=> false
> hoge.errors.full_messages
=> ["Nameは20文字以内で入力してください。"]

ユニーク(uniqueness)

# name単体でユニーク
validates :name, uniqueness: true

# name/emailでユニーク
validates :name, uniqueness: { scope: [:email] }

> Hoge.all
  Hoge Load (0.5ms)  SELECT "hoges".* FROM "hoges"
=> [#<Hoge:0x00007fe60d860628 id: 1, name: "", age: 0, gender: "man", birthday: Mon, 01 Jan 1900, created_at: Tue, 24 Sep 2019 20:19:16 JST +09:00, updated_at: Tue, 24 Sep 2019 20:19:16 JST +09:00>,
 #<Hoge:0x00007fe60d831c38 id: 2, name: "opiyo", age: 30, gender: "man", birthday: Mon, 01 Jan 1900, created_at: Tue, 24 Sep 2019 20:58:20 JST +09:00, updated_at: Tue, 24 Sep 2019 20:58:20 JST +09:00>]
> hogehoge = Hoge.new
=> #<Hoge:0x00007fe604662938 id: nil, name: "", age: 0, gender: "man", birthday: Mon, 01 Jan 1900, created_at: nil, updated_at: nil>
> hogehoge.name = 'opiyo'
=> "opiyo"
> hogehoge.age = 20
=> 20
> hogehoge.save
   (0.3ms)  BEGIN
  Hoge Exists (0.4ms)  SELECT  1 AS one FROM "hoges" WHERE "hoges"."name" = $1 LIMIT $2  [["name", "opiyo"], ["LIMIT", 1]]
   (0.3ms)  ROLLBACK
=> false
> hogehoge.errors.full_messages
=> ["Nameはすでに存在します。"]

数値のみ(numericality)

validates :age, numericality: true

> hoge.age = 'opiyo'
=> "opiyo"
> hoge.valid?
=> false
> hoge.errors.full_messages
=> ["Ageは数値で入力してください。"]

正規表現(format)

validates :postal_code, format: { with: /\A[0-9]{3}-[0-9]{4}\z/ }

> hoge.postal_code = '1234-567'
=> "1234-567"
> hoge.valid?
=> false
> hoge.errors.full_messages
=> ["Postal codeは不正な値です。"]

条件付き(if:、on:)

# カラムの値やメソッドなどで条件を設定する時
validates :name, presence: true, if: Proc.new { age < 20 }

# アクションに応じて設定する時
validates :postal_code, format: { with: /\A[0-9]{3}-[0-9]{4}\z/ }, on: update

> hoge.age = 18
=> 18
> hoge.save
   (0.3ms)  BEGIN
   (0.3ms)  ROLLBACK
=> false
> hoge.errors.full_messages
=> ["Name入力してください。"]
> hoge.age = 30
=> 30
> hoge.valid?
=> true

バリデーションを自分で作る(validate、with_options)

# メソッドを作ってチェックする
validate :enter_name_if_minor

def enter_name_if_minor
  if self.age < 20
    errors[:base] << '未成年の場合は名前を入力してください!'
  end
end

> hoge.age = 18
=> 18
[3] pry(main)> hoge.valid?
=> false
[4] pry(main)> hoge.errors.full_messages
=> ["Age未成年の場合は名前を入力してください!"]

# 別のバリデーションを作る(メール送る処理の時だけ使うバリデーション)
with_options on: :send_mail do
  validates :email, presence: true
end

> hoge.valid?(:send_mail)
=> false
> hoge.errors.full_messages
=> ["Email入力してください。"]
> hoge.valid?
=> true

# saveを使うときは`context`を使う
> hoge.save(context: :send_mail)

その他

その他にも複雑なvalidationが必要な場合に色々なgemがあったりします。

  • phonelib(電話番号のvalidation)
  • email_validator(メールアドレスのvalidation)

上記で紹介した正規表現(:format)を使えば実現できるのですが、電話番号やメールアドレスは結構考えることが多く大変です。 こういう場合は、だいたいgemがあるのでそれを使えばokだと思います!

Rails最高ですね。