ユニーク対応時の色々なパターンでよく詰まるのでメモします。
通常のユニーク
class User < ApplicationRecord
# 省略
validates :code, uniqueness: true
end
# 省略
t.string 'code'
t.index ["code"], name: "code", unique: true
end
これが一般的によく記事にあるような書き方ですが、
これだとうまく行かないことが多いと思います。
論理削除は見ないユニーク
class User < ApplicationRecord
# 省略
validates :code, allow_blank: true,
uniqueness: { scope: :deleted_at }
end
# 省略
t.string 'code'
t.index ["code", "deleted_at"], name: "code", unique: true
end
複合ユニークにすることによって、
codeカラムとdeleted_atカラム両方を見て
データが全く同じものがあればバリデーションが発生。
deleted_atは年月日から秒数まで見るので、
deleted_atカラムにデータが入っている時点で被ることは無いです。
つまり、
**「(論理)削除してないけどcodeが被った」**データがあった時に codeカラムが同じ、deleted_at`カラムが同じ(nil同士)となって
バリデーションが発生します。
nilだけ見ないユニーク
class User < ApplicationRecord
# 省略
validates :code, uniqueness: { allow_blank: true, conditions: -> { with_deleted } }
before_save :code_to_empty
private
# ""(空文字)をnilに
def code_to_empty
self.code = nil if code.blank?
end
end
# 省略
t.string "code", null: true
t.index ["code"], name: "code", unique: true
end
nilを見ない場合はこちらです。
1番上のやり方に近いですが、
実装時にバリデーションがうまく通りませんでした。
原因としては、
nilがどこかで""(空文字)になってしまい、
「ユニークはnilを見ない」という性質をうまく適用できず、
モデル側のバリデーションがうまく使えなかったです。
そのためメソッドを作って
""(空文字)を無理矢理nilに変更しています。
これで通るようになりました。