122
117

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

【Rails】「テーブルのカラムに定義するNot Null制約」と「モデルに定義するバリデーション(presence: true)」の挙動の違い。

Last updated at Posted at 2019-02-06

Railsの勉強をしていてハマったのでメモ。

あるテーブルのカラムについて、マイグレーションファイル自体に「null: false」を記入してNotNull制約をする場合と、モデル自体に「validates :カラム名, presence: true」としてnull不許可をする場合、挙動に違いがあります。

「どちらも値にnilが入らないんでしょ? 同じじゃん。」と思いがちですが、たとえば拒否できるデータ構造が異なっており、前者では「空文字("")」を拒否することが出来ません。

以下でその挙動について詳しくみていきます。

前者の場合(testsテーブルのtitleカラムにNotNull制約を付与)

migration.rb
class CreateTests < ActiveRecord::Migration[5.2]
  def change
    create_table :tests do |t|
      t.string :title, null: false

      t.timestamps
    end
  end
end

このマイグレーションファイルをbin/rails db:migrateしたあと、Railsコンソールにてnilを保存しようとすると、以下のように弾かれます。

irb(main):001:0> Test.new(title: nil).save
(中略)
ActiveRecord::NotNullViolation (PG::NotNullViolation: ERROR:  null value in column "title" violates not-null constraint)

これは想像通りの挙動ですね。
ただし、以下のように「空文字("")」でsaveしたとき、保存できてしまいます。

irb(main):001:0> Test.new(title: "").save
   (0.3ms)  BEGIN
  Test Create (0.6ms)  INSERT INTO "tests" ("title", "created_at", "updated_at") VALUES ($1, $2, $3) RETURNING "id" \
[["title", ""], ["created_at", "2019-02-05 10:02:31.263051"], ["updated_at", "2019-02-05 10:02:31.263051"]]
   (0.8ms)  COMMIT
=> true

NotNull制約したのに!

ここがポイントで、バリデーションでnull不許可した場合は、空文字も拒否することができます。

model.rb
class Test < ApplicationRecord
  validates :title, presence: true
end
irb(main):001:0> test = Test.new(title: "")
irb(main):002:0> test.save
   (0.2ms)  BEGIN
   (0.2ms)  ROLLBACK
=> false

これで安心ですね。
また、バリデージョンに基づいて保存に失敗した場合、このtestインスタンスにはバリデーションに基づいたエラー情報が入力されます。

irb(main):003:0> test.errors
=> #<ActiveModel::Errors:0x00007f9734965cf8 \
@base=#<Test id: nil, title: "", created_at: nil,  updated_at: nil>, \ 
@messages={:title=>["を入力してください"], @details={:title=>[{:error=>:blank}]}>

@messages変数と、@details変数に情報が入ってますね。
(rails-i18nにより日本語化済み)

これは以下のようにして配列の形で取り出したり出来ます。

irb(main):004:0> test.errors.full_messages
=> ["titleを入力してください"]

バリデーションでのエラーの方が特殊な挙動をしますが、情報を取り出せるので便利です。

まとめ

・テーブルのカラムに定義するNotNull制約はnilは拒否するが、空文字は拒否しない。
・モデルに定義するバリデージョンでのnull不許可(presence: true)は、nilも空文字も拒否する。そして、拒否したときに(saveに失敗したときに)インスタンスの@messages変数と@details変数の中に定義したバリデーションに基づいた拒否理由を返す。
・nullを許可したくない場合は、ひとまずテーブルもモデルもnull不許可にしておくのがよさそう。

122
117
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
122
117

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?