すでにメンバーの場合は

無料会員登録

GitHubアカウントで登録 Pikawakaが許可なくTwitterやFacebookに投稿することはありません。

登録がまだの方はこちらから

Pikawakaにログイン

GitHubアカウントでログイン Pikawakaが許可なくTwitterやFacebookに投稿することはありません。

Rails

【Rails】Railsのバリデーションの使い方をマスターしよう!

ぴっかちゃん
ぴっかちゃん

バリデーションとは、データベースに保存する前に保存する内容を検証する機能です。

投稿フォームで何かを投稿するとき、「入力必須項目」というのを目にしたことはないでしょうか?
例えば名前やメールアドレス、パスワードなどです。

このとき、入力必須項目に何も入力しないで投稿をすると投稿ができないようにしたいですね。

バリデーション

他にもパスワードなどは4文字以上であるとか、すでにデータベースに存在しているメールアドレスで登録できないようにするとかデータを保存する前に検証したいときもあります。

そんなときに定義するのがバリデーションです。
バリデーションを定義すると、データベースに保存する際、投稿された内容を検証し、保存するかどうかをチェックできるようになります。

バリデーションの使い方

この章では、バリデーションの使い方について解説します。

バリデーションのトリガ

バリデーションを定義すると、下記のメソッドが動く前に必ず検証が行われます。

  • save
  • save!
  • create
  • create!
  • update
  • update!

saveupdateは検証でデータが保存されない場合はfalseを返します。

ターミナル
1
2
3
4
5
6
[1] pry(<UsersController>)> @user.save
(0.2ms) BEGIN
(pry):1
(0.2ms) ROLLBACK
(pry):1
=> false
ターミナル
1
2
3
4
5
6
[1] pry(<UsersController>)> @user.update(user_params)
(0.2ms) BEGIN
(pry):1
(0.2ms) ROLLBACK
(pry):1
=> false

createは保存されてもされなくてもオブジェクト自身を返します。

ターミナル
1
2
3
4
5
6
7
8
9
10
[1] pry(<UsersController>)> User.create(name: "")
(0.2ms) BEGIN
(pry):1
(0.4ms) ROLLBACK
(pry):1
=> <User:0x007fd8bf0d0d28
id: nil,
name: "",
created_at: nil,
updated_at: nil>

またそれぞれのメソッド名の最後に!をつけると保存されなかった場合に例外処理を返します。
つまりfalseやインスタンスが返ってくるのではなく、エラー文が表示されます。
なんで保存がされないのか疑問に思うときは!を付けてエラーの原因を探ってみましょう。

ターミナル
1
2
3
4
5
6
7
8
9
10
11
12
[1] pry(<UsersController>)> user = User.create(name: "")
=> <User:0x007fd8bf0d0d28
id: nil,
name: "",
created_at: nil,
updated_at: nil>
[2] pryy(<UsersController>)> user.save!
(0.2ms) BEGIN
User Exists? (0.2ms) SELECT 1 AS one FROM `users` WHERE `users`.`name` IS NULL LIMIT 1
(0.2ms) ROLLBACK
ActiveRecord::RecordInvalid: バリデーションに失敗しました: Nameを入力してください
# 上のメッセージが表示される

バリデーションを定義してみよう

それではバリデーションを定義してみましょう。
バリデーションはモデルでも定義できますし、マイグレーションファイル に定義することもできます。

モデルに記入

モデルでバリデーションを定義するには下記のように記述します。

モデル
1
validates :カラム名, ヘルパー

ヘルパーを使うとどういう検証を行うのかを定義することができます。
ヘルパーの部分は後述します。

下記のように同時に複数のカラムを指定することもできます。

モデル
1
validates :カラム名, :カラム名, :カラム名, ヘルパー

マイグレーションファイル に記入

入力必須という検証をモデルに定義しただけだとRails側で保存させないだけなので、投稿フォーム経由での保存はできなくさせますが、SQLから実行するとデータは保存できてしまいます。
ですのでSQL側でも検証をさせておくと安心です。

その場合はマイグレーションファイル にバリデーションの定義をします。
マイグレーションファイル には下記のように記述します。

マイグレーションファイル
1
t.カラムの型 :カラム名, null: false

このようにnull: falseと指定すると入力必須になります。
t.timestampsだけはデフォルトでnull: falseになっています。
入力必須でない場合はnull: trueにすればOKです。

例えばnicknameカラムにnull: falseとしておくとnicknameカラムが空の時にはsqlの方で検証をしてROLLBACKさせ、保存されなくさせます。

ターミナル
1
2
3
4
5
6
7
8
9
10
Parameters: {"utf8"=>"✓", "user"=>{"nickname"=>"", "commit"=>"送信"}
(2.6ms) BEGIN
↳ app/controllers/users_controller.rb:11
User Create (1.6ms) INSERT INTO `users` (`nickname`, `created_at`, `updated_at`) VALUES ('', 1, '2019-08-18 07:27:54', '2019-08-18 07:27:54')
↳ app/controllers/users_controller.rb:11
(0.2ms) ROLLBACK
↳ app/controllers/users_controller.rb:11
Completed 500 Internal Server Error in 13ms (ActiveRecord: 4.4ms)
ActiveRecord::NotNullViolation (Mysql2::Error: Field 'nickname' doesn't have a default value: INSERT INTO `users` (`nickname`, `created_at`, `updated_at`) ):

バリデーションのヘルパー

railsにはバリデーションのヘルパーが多数用意されています。
このヘルパーを使うとどういう検証を行うかを定義することができます。
主なヘルパーを紹介します。

presence

一番多く使うヘルパーです。
定義すると「空でないか」を検証します。

モデル
1
validates :カラム名, presence: true

下記のように記述するとnameカラムにちゃんと値が入っているかを検証します。

モデル
1
validates :name, presence: true

なのでこのカラムに何も入っていないときは保存されません。

上のコードはvalidates_presence_ofを使って書くこともできます。

モデル
1
validates_presence_of :name

absence

presenceとは逆で定義すると「空であるか」を検証します。

モデル
1
validates :カラム名, absence: true

上のコードはvalidates_absence_ofを使って書くこともできます。

モデル
1
validates_absence_of :カラム名

uniqueness

値が一意(unique)であり重複していないかを検証します。
メールアドレスなど重複しては困るときに使います。

モデル
1
validates :カラム名, uniqueness: true

このように定義するとカラムにすでに存在している内容と同じものがあるかどうかを検証することができます。
すでに値が存在しているものと同じ値であれば保存されません。

上のコードはvalidates_uniqueness_ofを使って書くこともできます。

モデル
1
validates_uniqueness_of :カラム名

acceptance

チェックボックスがオンになっているかどうかを検証します。
サービスに対する利用条項を読んで、「同意した」などのチェックボックスがあるサイトをよく見かけるかと思います。
そのときチェックボックスにチェックが入っているかを検証するときなどに利用します。

モデル
1
validates :カラム名, acceptance: true

上のコードはvalidates_acceptance_ofを使って書くこともできます。

モデル
1
validates_acceptance_of :カラム名

confirmation

2つのフォームで入力された内容が完全に一致するかを検証します。
メールアドレスのフォームとメールアドレスの確認フォームのが全く同じであるか検証したいときなどに利用します。

モデル
1
validates :カラム名, confirmation: true

一致しているか確認したいもう一つのカラム名は末尾に_confirmationをつけます。
こちらはフォームでのみ使うカラムなのでデータベースにこのカラムを作成する必要はありません。

メールアドレスを例にすると下記のようになります。

モデル
1
validates :email, confirmation: true

このように記述するとemailカラムemail_confirmationカラムの入力値が一致しているかを検証します。

上のコードはvalidates_confirmation_ofを使って書くこともできます。

モデル
1
validates_confirmation_of :email

inclusion

値が指定した文字になっているかを検証します。
基本的にinオプションと一緒に使用します。

モデル
1
2
3
4
5
# 1つのワードの指定
validates :カラム名, inclusion: { in: ["検証したい文字"] }
# 複数のワードの指定
validates :カラム名, inclusion: { in: ["検証したい文字1", "検証したい文字2"] }

上の複数のワードの指定時のコードは下記のように%記法を使って書くこともできます。

モデル
1
validates :カラム名, inclusion: { in: %w(検証したい文字1 検証したい文字2) }
%記法(パーセント記法)

コードをシンプルに記述することができる記法です。
%iはシンボルの配列を作り出すことができます。
%wとすると文字列の配列を作り出すことができます。

記述するコードが減るので、可読性が上がったりタイプミスによるエラーが出る確率を減らすことができます。

数字の1から100まで以外は保存されないようにするには下記のように記述します。

モデル
1
validates :size, inclusion: { in: 1..100 }

上のコードはvalidates_inclusion_ofを使って書くこともできます。

モデル
1
validates_inclusion_of :size, in: 1..100

他にも例えば文字の大きさを入力するフォームがあったとき、必ず「大」、「中」、「小」のいずれかの文字にしてもらいたい場合があるとします。
その時は下記のように指定すればこの3文字以外の文字が入力された時には保存がされないようにすることができます。

モデル
1
validates :size, inclusion: { in: %w(大 中 小) }

この時はたとえ「特大」のように文字の中に「大」が入っていたとしても「大」か「中」か「小」という文字でなければ保存されません。
このように特定の文字だけを保存したい時に使用します。

exclusion

指定された値が含まれていないかを検証します。
基本的にinオプションと一緒に使用します。

モデル
1
validates :カラム名, exclusion: { in: ["検証したい文字1", "検証したい文字2"] }

上のコードは下記のように%記法を使って書くこともできます。

モデル
1
validates :カラム名, exclusion: { in: %w(検証したい文字1 検証したい文字2) }

保存したくない文字を指定する時に使います。
例えばユーザー名に「管理人」とつけて欲しくない場合は下記のように指定します。

モデル
1
validates :name, exclusion: { in: %w(管理人) }

このようにすると「管理人」と入力された場合は保存されないようにすることができます。
「管理人A 」の場合は保存されます。

上のコードはvalidates_exclusion_ofを使って書くこともできます。

モデル
1
validates_exclusion_of :name, in: %w(管理人)

length

値の長さを検証します。
下記のオプションを使って定義します。

オプション名 内容
minimum 最小値を指定します
maximum 最大値を指定します
in 長さの範囲を指定します
is 値の長さを指定します

具体的には下記のように使用します。

モデル
1
2
3
4
validates :カラム名, length: { minimum: 2 } #最低でも2文字以上であるか
validates :カラム名, length: { maximum: 6 } #6文字以内であるか
validates :カラム名, length: { in: 5..10 } #5文字から10文字以内であるか
validates :カラム名, length: { is: 5 } #5文字であるか

上のコードはvalidates_length_ofを使って書くこともできます。

モデル
1
validates_length_of :first_name, maximum: 6

format

正規表現と属性の値が合致するかの検証をします。
withオプションと併用して使います。

モデル
1
validates :カラム名, format: { with: 正規表現 }

下記のように正規表現で指定します。

モデル
1
validates :name, format: { with: /\A[a-zA-Z]+\z/ }

上のコードは半角英文字だけ入力を許可することを意味します。
なので「pikawaka」は保存され、「ピカわか」だと保存されません。

上のコードはvalidates_format_ofを使って書くこともできます。

モデル
1
validates_format_of :name, with: /\A[a-zA-Z]+\z/

よく使う正規表現をまとめてみます。
用途に書いてある書式だけ入力可能です。
※「全角ひらがな・カタカナ」であれば全角のひらがなとカタカナどちらかだけ入力可能

用途 正規表現
全角ひらがな /\p{hiragana}/
/\A[ぁ-んー-]+\z/
全角カタカナ /\p{katakana}/
/\A[ァ-ヶー-]+\z/
半角カタカナ /\A[ァ-ン゙゚]+\z/
全角ひらがな・カタカナ /\A[ぁ-んァ-ヶー-]+\z/
漢字 /\A[一-龥]+\z/
全角ひらがな・漢字 /\A[ぁ-ん一-龥]/+\z
全角ひらがな・カタカナ・漢字 /\A[ぁ-んァ-ヶ一-龥]/+\z
半角英文字(小文字) /\A[a-z]+\z/
半角英文字(大文字) /\A[A-Z]+\z/
半角英文字(全て) /\A[a-zA-Z]+\z/
/\A[a-z]+\z/i
全角数値 /\A[0-9]+\z/
半角数値 /\A[0-9]+\z/
半角小文字英文字・数値 /\A[a-z0-9]+\z/
半角大文字英文字・数値 /\A[A-Z0-9]+\z/
半角英文字・数値 /\A[a-zA-Z0-9]+\z/
/\A[a-z0-9]+\z/i
半角英数字を両方含む /\A(?=.*?[a-z])(?=.*?\d)[a-z\d]+\z/i
郵便番号(ハイフンあり7桁) /\A\d{3}[-]\d{4}\z/
郵便番号(ハイフンなし7桁) /\A\d{7}\z/
email /\A([^@\s]+)@((?:[-a-z0-9]+.)+[a-z]{2,})\z/i
/\A[\w+-.]+@[a-z\d-]+(.[a-z\d-]+)*.[a-z]+\z/i
など
電話番号(ハイフンなし10・11桁) /\A\d{10,11}\z/

例えばパスワードに半角英数字を両方含めなければいけないときは下記のように記述します。

モデル
1
validates :password, format: { with: /\A(?=.&#042;?[a-z])(?=.&#042;?\d)[a-z\d]+\z/i }

numericality

属性に数値のみが使われているかを検証します。

モデル
1
validates :カラム名, numericality: true

オプションとして以下の制約を指定することができます。

オプション 制約
:greater_than 指定された値よりも大きくなければならないことを指定
:greater_than_or_equal_to 指定された値と等しいか、それよりも大きくなければならないことを指定
:equal_to 指定された値と等しくなければならないことを指定
:less_than 指定された値よりも小さくなければならないことを指定
:less_than_or_equal_to 指定された値と等しいか、それよりも小さくなければならないことを指定
:other_than 渡した値以外の値でなければならないことを指定
:odd trueにすると奇数であるかを指定
:even trueにすると偶数であるかを指定

例えば100以上1,000以下の場合は下記のように記述します。

モデル | 範囲の指定
1
validates :price, numericality: { greater_than_or_equal_to: 100, less_than_or_equal_to: 1_000 }

上のコードはvalidates_numericality_ofを使って書くこともできます。

モデル
1
validates_numericality_of :price, greater_than_or_equal_to: 100, less_than_or_equal_to: 1_000

バリデーションのオプション

オプションには全てのヘルパーで使うことができるものがあります。

on

検証が行われるタイミングを指定することができます。

モデル
1
validates :カラム名, ヘルパー, on: :メソッド名

下のように記述するとcreateメソッドが実行されたときにだけ一意かどうかの検証が行われます。

モデル
1
validates :email, uniqueness: true, on: :create

saveメソッドはcontextパラメータでon: :hogehogeの部分を指定することができます。

railsファイル
1
2
3
4
5
6
# モデル
validates :email, uniqueness: true, on: :create_account
# コントローラー
@user.save(context: :create_account)
# 独自に作成したcreate_acountというメソッドが実行された時にだけ検証が行われる

例えばユーザー登録の時はemailと名前だけ保存されれば良いけれど、その後、商品を購入したい時にはユーザー編集ページでユーザー情報に住所を入力する必要があるとします。
その際には下記のようにすれば更新する時にだけ住所が入力されているかを検証させることができます。

モデル
1
2
3
4
5
6
# emailとnameは作成時と更新時どちらも検証が行われる
validates :email, presence: true
validates :name, presence: true
# addressは更新時のみ検証が行われる
validates :address, presence: true, on: :update

message

バリデーションが失敗した時に全てのエラーメッセージが入っているerrorsコレクションという場所に自分で作成したカスタムエラーメッセージを追加することができるオプションです。
指定をしていない場合はデフォルトで用意されているメッセージが表示されます。

モデル
1
validates :カラム名, ヘルパー: { message: "出力されるメッセージ" }

下記のように記述するとnameカラムがnilだった場合のエラーメッセージが「名前を入力してください」となります。

モデル
1
validates :name, presence: { message: "名前を入力してください" }

エラーメッセージとは何かについては後述するerrorsメソッドを参照してください。

allow_nil

値がnilの場合、検証を行わなくすることができます。

モデル
1
validates :カラム名, ヘルパー, allow_nil: true

何も入力しないときは検証したくない場合は下記のように記述します。

モデル
1
validates :name, length: { minimum: 2 }, allow_nil: true

allow_blank

nilや空文字など値がblank?に該当する場合、検証を行わなくすることができます。
上のallow_nilと似ていますが、allow_nilは空文字を入力した場合(フォームに何も入力しなかった場合)は検証をしてしまいますが、allow_blankだと検証をスキップさせることができます。

モデル
1
2
3
4
5
6
7
# この場合空文字を入力するとバリデーションの検証が実行される
# つまり1文字以下の場合は検証が行われROLLBACKされる
validates :name, length: { minimum: 2 }, allow_nil: true
# この場合空文字を入力するとバリデーションの検証はスキップされる
# つまり1文字以下の場合の検証がスキップされるので保存される
validates :name, length: { minimum: 2 }, allow_blank: true

便利なメソッド

その他の便利なメソッドを紹介します。

valid?

検証をするメソッドです。
検証は上で紹介したメソッドが実行される前に行われますが、このメソッドを使用しても検証が実行されます。

例えばnewメソッドは実行時には検証が行われないので、newされた時に保存されるかを確認したい時に使用します。
もし保存できる状態であればtrueが、検証で引っかかって保存できない状態であればfalseが返ります。

モデル
1
validates :text, presence: true
ターミナル
1
2
3
4
5
6
7
8
9
[1] pry(main)> article = Article.new(text: "")
=> #<Article:0x007fc5b3b6d6d8
id: nil,
text: "",
created_at: nil,
updated_at: nil,
user_id: nil>
[2] pry(main)> article.valid?
=> false

詳しい使い方については後述します。

invalid?

valid?メソッドの逆の返り値を返すメソッドです。
保存できる状態であればfalseが、検証で引っかかって保存できない状態であればtrueが返ります。

errors

valid?メソッドを使った後にerrorsメソッドを使うと今起きているエラーをエラーメッセージとして確認することができます。

モデル
1
validates :text, presence: true
ターミナル
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[1] pry(main)> article = Article.new(text: "")
=> #<Article:0x007fc5b3b6d6d8
id: nil,
text: "",
created_at: nil,
updated_at: nil,
user_id: nil>
[2] pry(main)> article.valid?
=> false
[3] pry(main)> article.errors
=> #<ActiveModel::Errors:0x007fc5b85158f8
@base=
#<Article:0x007fc5b3b6d6d8
id: nil,
text: "",
created_at: nil,
updated_at: nil,
user_id: nil>,
@messages={:text=>["can't be blank"]}>

最後の行がエラーメッセージです。
ここではcan't be blank、つまり「空白にはできません」という原因で保存されていないのがわかります。
このようにどのような検証が行われて保存できないかをエラーコードで確認することができます。

errors[:カラム名]

カラムを指定してエラーメッセージを確認することができます。

モデル
1
validates :text, presence: true
ターミナル
1
2
3
4
5
6
7
8
9
10
11
[1] pry(main)> article = Article.new(text: "")
=> #<Article:0x007fc5b3b6d6d8
id: nil,
text: "",
created_at: nil,
updated_at: nil,
user_id: nil>
[2] pry(main)> article.valid?
=> false
[3] pry(main)> article.errors[:text]
=> ["can't be blank"]

errors.messages

カラムごとのエラーメッセージを一度に確認することができます。

モデル
1
validates :text, presence: true
ターミナル
1
2
3
4
5
6
7
8
9
10
11
12
[1] pry(main)> article = Article.new(title: "hoge", text: "")
=> #<Article:0x007fc5b3b6d6d8
id: nil,
title: "hoge",
text: "",
created_at: nil,
updated_at: nil,
user_id: nil>
[2] pry(main)> article.valid?
=> false
[3] pry(main)> article.errors.messages
=> {:text=>["can't be blank"]}

errors.full_messages

詳細なエラーメッセージを確認することができます。

モデル
1
validates :text, presence: true
ターミナル
1
2
3
4
5
6
7
8
9
10
11
[1] pry(main)> article = Article.new(text: "")
=> #<Article:0x007fc5b3b6d6d8
id: nil,
text: "",
created_at: nil,
updated_at: nil,
user_id: nil>
[2] pry(main)> article.valid?
=> false
[3] pry(main)> article.errors.full_messages
=> ["Text can't be blank"]

バリデーションに条件をつけよう

オプションを使うと条件付きで検証を行うことができます。

:if

特定の条件の時に検証を行う必要がある時に使用するオプションです。

モデル
1
validates :カラム名, ヘルパー, if: :条件式やメソッド

deviseで定義されているuser_signed_in?というヘルパーメソッドを使ってユーザーがログインしている時だけ検証をすることができたりします。

モデル
1
validates :text, presence: true, if: :user_signed_in?

:unless

ifオプションと同じで特定の条件の時に検証を行う必要がある時に使用するオプションです。
ifとは逆で条件式がfalseの時にだけ検証をすることができます。

with_options

1つの条件を複数のバリデーションで使いたい時に使用します。

モデル
1
2
3
4
with_options ヘルパー do
validates :カラム名, ヘルパー
validates :カラム名, ヘルパー
end

このように指定します。

モデル
1
2
3
4
with_options presence: true, format: { with:/\A[-ヶー-]+\z/, message: '全角カタカナで入力してください' } do
validates :first_name_kana
validates :last_name_kana
end

上のように記述するとfirst_name_kanaカラムとlast_name_kanaカラムの両方に値が存在しなければならないことと、全角カタカナで入力をしないといけない制限を一括でかけることができます。

バリデーションの流れ

検証された結果、入力必須なのに値がなかったので保存時にROLLBACKがかかり、保存されません。

ターミナル
1
2
3
4
User Load (0.4ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 ORDER BY `users`.`id` ASC LIMIT 1
User Load (0.8ms) SELECT `users`.* FROM `users`
(0.1ms) BEGIN
(5.5ms) ROLLBACK

このようにターミナルのログを確認するとバリデーションに引っかかって、保存されないことが確認できます。

予期しないエラーが起きてdebugする場合

データが保存される際、こちらが予期しない理由で検証が行われ、データが保存されずROLLBACKされてしまう場合があります。
この時にどういう理由でROLLBACKされてしまうのか原因を探る必要があります。

その際に使うメソッドが先ほど紹介したvalid?メソッドです。

今回は投稿フォームにちゃんと登録をしたのにも関わらず登録ができない時の例をみてみましょう。

登録できない例

ちゃんと値を入力しているのに保存ができない理由がなぜだかわかりません。

ターミナル
1
2
3
4
5
6
7
8
Processing by UsersController#create as HTML
Parameters: {
# 中略
"user"=>{"name"=>"管理人", "sex"=>"男", "age"=>"55", "tall"=>"167", "weight"=>"45"}, "commit"=>"送信"}
(0.9ms) BEGIN
↳ app/controllers/users_controller.rb:11
(0.2ms) ROLLBACK
↳ app/controllers/users_controller.rb:11

このように予期せぬエラーが起きたときにどうやってデバッグするか、エラーの原因を調査する流れを説明していきます。

今回のモデルの記述は下記のようになっています。

user.rb
1
2
3
4
class User < ApplicationRecord
validates :name, presence: true
belongs_to :job
end

バリデーションはnameカラムが入力必須ということしか定義していません。
今回はちゃんとnameカラムに値が入力されているのになぜか検証で引っかかってしまいました。

理由がわからないので、まずはcreateアクション内でbinding.pryを使い処理を止めます。
次に保存するインスタンスにvalid?メソッドで検証が行われて保存できる状態か、できない状態かを調べます。
返り値がfalseなので検証で保存できない状態だということが確認できました。

ターミナル
1
2
[1] pry(main)> @user.valid?
=> false

valid?メソッドを使用するとそのインスタンスがerrorsメソッドを使用した時にエラーメッセージを確認することができるようになります。
では実際どんなエラーメッセージが出ているか確認してみます。

ターミナル
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[2] pry(main)> @user.errors
=> #<ActiveModel::Errors:0x007fdcb2db3620
@base=
#<User:0x007fd522d08870
id: nil,
name: "管理人",
job_id: nil,
sex: "男",
age: 55,
tall: 167.0,
weight: 45.0,
created_at: nil,
updated_at: nil>,
@details={:job=>[{:error=>:blank}]},
@messages={:job=>["must exist"]}>

このようにmust existというメッセージが確認できました。
このメッセージは「jobカラムの値が存在しなければならない」という意味です。
今回は存在しないjobカラムに対するエラーが表示されました。

色々調べるとrails5からアソシエーションを定義しているとusersテーブルとjobsテーブルとの結びつけをするjob_idというカラムにデフォルトで入力必須のバリデーションが定義されてしまうためだということがわかりました。
この場合アソシエーションの定義であるbelongs_toのバリデーションに引っかかるとjob_idではなく、モデル名であるjobがエラーメッセージ内に表示されるようです。

外部キーのバリデーション

Rails4まではアソシエーションを定義した時のbelongs_toの外部キーでnilを許可しないようにbelongs_to :user, required: trueなどのバリデーションを手動で定義していました。
ですが、rails5からは自動でnilを許可しないバリデーションが定義されるようになりました。
これを意図的に外すには下記のように定義します。

モデル
1
belongs_to :user, optional: true

このように自動でnilを許可しないバリデーションが定義されているので、投稿フォームに職業を登録するフォームを追加し、job_idも保存できるようにすることによって今回のエラーを解決することができました。

フォームを変更

このようにvalid?メソッドerrorsメソッドを使用することで原因を特定することができます。

もしvalid?メソッドを使用していないインスタンスにerrorsメソッドを使用すると下記のようにmessageの部分は何も入っていない状態になってしまい、エラーメッセージが確認できません。

ターミナル
1
2
3
4
5
6
7
8
9
10
11
12
13
14
=> #<ActiveModel::Errors:0x007fcb3ceb9c68
@base=
#<User:0x007fcb3e594848
id: nil,
name: "ピカわか",
job_id: 5,
sex: 0,
age: 25,
tall: 170.0,
weight: 85.0,
created_at: nil,
updated_at: nil>,
@details={},
@messages={}>

もし原因不明でROLLBACKが起きて値を保存できない場合は、valid?メソッドerrorsメソッドを利用して原因を確認してみましょう。

意図的にエラーを起こす場合

データを保存する際、保存できた時とできなかった時で処理を分けたい場合があります。
データを保存するメソッドにcreateとsaveメソッドがありますが、saveメソッドを使うと検証で引っかかった時にfalseが返ります。
これはvalid?メソッドを使った時と同じことをsaveメソッドが行なっているからです。

ターミナル
1
2
3
4
5
6
7
8
9
10
11
[1] pry(main)> article = Article.new(text: "")
=> #<Article:0x007fc5b8079830
id: nil,
text: "",
created_at: nil,
updated_at: nil,
user_id: nil>
[2] pry(main)> article.save
(132.4ms) BEGIN
(8.2ms) ROLLBACK
=> false

valid?メソッドを使った時と同じことをsaveメソッドが行なっているということはsaveメソッドを使っても、errorsメソッドを使用した時にエラーメッセージを確認することができます。

ターミナル
1
2
3
4
5
6
7
8
9
10
[3] pry(main)> article.errors
=> #<ActiveModel::Errors:0x007fc5b8020118
@base=
#<Article:0x007fc5b8079830
id: nil,
text: "",
created_at: nil,
updated_at: nil,
user_id: nil>,
@messages={:text=>["can't be blank"]}>

そして検証で引っかかるとfalseが返るのでコントローラーでは下記のように条件分岐をさせることができます。

コントローラー
1
2
3
4
5
6
7
8
def create
@article = Article.new(article_params)
if @article.save
redirect_to articles_path
else
render :new
end
end

ここでは保存がされればルートパスへ、保存が失敗すればもう一度投稿フォームが表示されるよう記述しています。

またデータベースに保存されなかった時に、ユーザーにエラーが出て保存がされなかったことをメッセージで知らせることができます。
その時に使うメソッドがerrors.any?メソッドです。
もしエラーが出て保存されない場合、返り値としてtrueが返ります。
それを利用してビューファイルには下記のように記述をします。

ビューファイル
1
2
3
<% if @article.errors.any? %>
<p class="red">名前を入力してください</p>
<% end %>

バリデーション

こうすることにより、ユーザーに保存ができなかったことを知らせることができます。

カスタムバリデーションを作ろう

今まで紹介してきた中に自分が実行したいバリデーションがない場合は自分で条件を作成することができます。
そのことをカスタムバリデーションと呼びます。
では実際に自分でバリデーションを定義する流れをみていきましょう。

まずはappフォルダ内にvalidatorsフォルダを作成します。
validatorsフォルダ内にはカスタムバリデーションを定義するファイルを作成します。
この時のファイル名はバリデーション名_validator.rbにします。

例えば下記のようにNameCheckという名前でバリデーションを作成した時のファイル名は
name_check_validator.rbとなります。

app/validators/name_check_validator.rb
1
2
3
class NameCheckValidator < ActiveModel::EachValidator
# バリデーションの内容
end

次にそのファイルに下記のようなコードを記述します。

バリデーション名_validator.rb
1
2
3
4
5
class バリデーション名Validator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
検証する内容
end
end

カスタムバリデーションを作成する際にはvalidate_eachメソッドを使用します。
3つの引数は順番に(保存する前のテーブルのレコード, カラム名, 送信した値)を表します。

実際には下記のように記述します。

app/validators/name_check_validator.rb
1
2
3
4
5
6
7
class NameCheckValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
if value == "管理人"
record.errors[attribute] << "その名前は使用できません"
end
end
end

次にこのバリデーションを有効にするためconfig/application.rbにカスタムバリデーションを定義したファイルがあるフォルダのパスを下記のように追加します。

config/application.rb
1
2
3
4
5
module アプリ名
class Application < Rails::Application
config.autoload_paths += Dir["#{config.root}/app/validators"]
end
end

使用する時には下記のように記述します。

モデル
1
validates :カラム名, バリデーション名: true

上の例だとバリデーション名をNameCheckとしているのでname_checkという形で使用します。

Userモデル
1
validates :name, name_check: true

この時カラム名は下記のように複数指定することもできます。

Userモデル
1
validates :name, :age, name_check: true

この時、実際どのようなことが行われている確認をしてみましょう。

レコードの内容
1
2
3
4
5
6
record=> #<User:0x00007ffcd811eef0
id: nil,
name: 'pikawaka',
age: 27,
created_at: nil,
updated_at: nil>

上のようなレコードがあったとします。
カスタムバリデーションはvalidate_each(record, attribute, value)メソッドで作成しました。
3つの引数は順番に(保存する前のテーブルのレコード, カラム名, 送信した値)が入るのでした。

上の例の場合def validate_each(record, attribute, value)のrecord、attribute、valueには下記の内容が入ります。

recordには上のレコードの内容が入ります。
attributeにはカラム名が入るのでここでは最初に指定した「name」が入ります。
valueには送信した値である「pikawaka」が入ります。

そして今回はカラムを2つ指定してるので、eachのように繰り返しになってます。
次は:ageの番です。
またvalidate_eachのメソッドが実行され下記の内容が引数にセットされます。

recordには1番目と同様に上のレコードの内容が入ります。
attributeにはカラム名が入るのでここでは「age」が入ります。
valueには送信した値である「27」が入ります。

このような流れで実行されます。

validateメソッドを使った方法

他にもvalidateメソッドを使用してバリデーションを作成することもできます。
validatesのようにsが付かないので気をつけましょう。

モデル
1
2
3
4
5
validate :メソッド名
def メソッド名
検証したいコード
end

実際は下記のように定義します。

モデル
1
2
3
4
5
6
7
validate :check_name
def check_name
if name == "管理人"
errors.add(:name, "その名前は使用できません")
end
end

一時的なバリデーションのスキップ方法

一時的に検証を行わないようにするには下記のように記述します。

コントローラー
1
メソッド(validate: false)

下記のように使います。

ターミナル
1
2
3
4
5
6
7
article = Arrticle.new(title: "")
article.save(validate: false)
# 検証がスキップされるので保存が実行される
(1.1ms) BEGIN
Arrticle Create (54.9ms) INSERT INTO `articles` (`title`, `created_at`, `updated_at`) VALUES ('', '20XX-XX-XX XX:XX:XX', '20XX-XX-XX XX:XX:XX')
(26.7ms) COMMIT
=> true

ただし思わぬバグが発生することがあるので、使う際は十分に注意しましょう。

この記事のまとめ

  • バリデーションとはデータベースにデータを保存するときに内容を検証してくれる機能のこと
  • ヘルパーを使うことで色々な検証を行うことができる
  • valid?メソッドとerrorsメソッドを使えばどの検証に引っかかっているかを確認することができる
  • カスタムバリデーションで自分で作成した検証を行うことができる

15

わかった!