弊社の若者のPRでコメントしたんだけれど、これは普通に記事にできるかなと思ったので転載する。
事の経緯
保存できないことの検証はわざわざupdateせずにvalid?で取れるからそうしてほしいと伝えたら、保存できることの検証までvalid?で済ませようとしたため、コメントしました。
以下、コメント。
保存できること、更新できることのテストをする場合はちゃんとsave, create, updateが成功することを確認してください。 valid?が通ったからといって、保存が成功するとは限りません。DB側の制約に引っかかる可能性があるからです。
覚えておいてほしいのですが、ソフトウェアに限らず、物事には様々はレイヤーがあります。 そこを意識してください。
Modelでのバリデーション
↓
DBでのバリデーション
↓
保存
Modelでのバリデーション
メリット
- ソースコードで管理できる
- テストが容易
- 複雑なロジックで検証できる
- 検証していることに名前をつけられる
デメリット
- psql等で直接データを登録されることには無力😥
- タイミングによっては検証をすり抜けてくる😵
- DBにユニーク制約なし、Model側だけでユニーク制約をつけている場合、同時にバリデーションを確認すると通ってしまう
データベースでのバリデーション(Unique制約, Not Null制約, Check制約等)
メリット
- 確実にデータの整合性を担保できる👍
- Model側でNot Null制約(
presence: true
)つけていてもpsqlならNullで登録できるが、DBでNot Null制約をつけておけばエラーを検知できる
- Model側でNot Null制約(
- 簡単な制約なら導入が楽😄
- Not Null制約、Unique制約などは簡単。マイグレーションファイル作成時に対応可能。
デメリット
- ソースコードで管理できない😩(極論をいうとできるけど難しい)
- 複雑なロジックの制約を作るのが難しい🤯
- 別テーブルのデータを条件にするとか…。
テストの方法
プログラム側からのテストなら、Modelのバリデーションのテストも、DBの制約のテストもできます。
Modelのバリデーションの検証
- record.valid?で可能
- DBへの保存がないぶん速い🚀
- save, create, updateをするとDBへの保存が発生するのでテストが遅くなる👎
- Modelの検証で保存できないことを確認するだけならvalid?で十分
- 本当に保存できることを確認したいときはsave, create, updateの結果を検証すること🙏
- DBの制約の検証も通過して保存ができる、ということ
DBの制約の検証
record.update_column(s)
等、あえてModelでの検証をしないメソッドを使うsave(validate: false)
でも可だが、update_column(s)
のほうが対象のカラム名がわかりやすい- DBの制約に引っかかったら例外が発生するのでそれを捕捉する
expect { record.update_column(:name, nil) # Not Null制約の検証 }.to raise_error(ActiveRecord::NotNullViolation)
以上です。