patorashのブログ

方向性はまだない

RailsでModelとDBの制約の検証をするときの方針について

弊社の若者の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のバリデーションのテストも、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)

以上です。