【Rails】ユニーク制約はマイグレーションファイルとモデルファイルの両方に書く

業務でRailsのテーブル実装をしていたんですが、ユニーク制約をマイグレーションファイルとモデルファイルのどちらに記述すれば良いか迷ってしまいました。

そこでこの記事では実務で得た知見をもとに、Railsアプリケーションにおいてユニーク制約をマイグレーションファイルとモデルファイルのどちらに書くべきかまとめます。

目次

ユニーク制約はマイグレーションファイルとモデルファイルの両方に書く

結論として、ユニーク制約はマイグレーションファイルとモデルファイルの両方に書くべきです。

それぞれ以下のように記述します。

モデルファイルへの記述例

class JobKeyword < ActiveRecord::Base
    validates :name, presence: true, uniqueness: true  # 一意制約を付与
    validates :enabled, presence: true, inclusion: [true, false]
end

マイグレーションファイルへの記述例

class CreateJobKeywords < ActiveRecord::Migration[6.1]
  def change
    create_table :job_keywords, comment: '仕事のキーワード' do |t|
      t.string       :name,      null: false, comment: 'キーワード名'
      t.boolean    :enabled, null: false, comment: '有効/無効'
      t.text          :note,       null: true,  comment: '備考'

      t.timestamps
    end
    add_index :job_keywords, :name, unique: true  # 一意制約を付与
  end
end

片方だけだとなぜダメなのか?

当初のぼくは、マイグレーションファイルかモデルファイルのどちらかにユニーク制約をかければ良いと思っていましたが、それだと以下の理由でNGなようです。

モデルファイルだけにユニーク制約を書いた場合

「モデルファイルだけにユニーク制約を書く = アプリケーションレベルでの制約のみ設ける」ということになります。これは以下の理由でNGです。

  • 全く同じ時間に登録されたらデータの重複を防げない
  • モデルファイルのバリデーションはスキップできてしまう(saveメソッドにvalidate: falseを引数として与える)
  • DB移行時に新たなDB側で一位性を保証する追加の作業が必要となる

「全く同じ時間に登録されるなんて実際ないでしょ」と昔のぼくは思っていましたが、大量にアクセスがあるサービスでは、そういったことも起こりうるなと実務を通じて感じています。

マイグレーションファイルだけにユニーク制約を書いた場合

「マイグレーションファイルだけにユニーク制約を書く = データベースレベルでの制約のみ設ける」ということになります。これは以下の理由でNGです。

  • データがデータベースに送信されてからでないと重複を防げない(モデルファイルにバリデーションがあればアプリケーションレベルで重複を防げる)
  • 条件付きバリデーションが実装できない(「下書き保存」ではバリデーションをスキップするけど「公開」ではバリデーションチェックをしたい、など)

これも昔のぼくは「データベースに送信されるときに防げるならそれでいいじゃん」と思っていました。しかし、アクセス数が膨大なサービスではデータベースへの負荷も考慮する必要があると理解できました。また条件付きのバリデーションも結構出てくる場面があるので、やはりマイグレーションファイルだけにユニーク制約をかけるのも良くないなと腹落ちしました。

まとめ

上記の理由により、モデルファイルとマイグレーションファイルの両方にユニーク制約を設定することで、堅牢かつ柔軟なバリデーションを実現できると学びました。

参考文献

Railsエンジニアにおすすめの記事

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

未経験でSESから従業員300名以上の自社開発企業に転職しました。業務や個人開発で直面した問題や、転職・学習の経験を発信していきます。

コメント

コメントする

目次