Thursday, November 29, 2007

validates_uniqueness_of is broken, and dangerous

Update: Progess. The Rails API docs now mention this problem. Thanks, Koz.


validates_uniqueness_of guarantees unique column values, just like a unique key constraint in a database, right? Wrong. It doesn't guarantee anything.

Suppose your model validates_uniqueness_of :user_name. Two requests come along, each in a separate process. Both try to save a new row with a user_name of 'foobar'.
  • Request 1 checks 'foobar' - no problem.

  • Request 2 checks 'foobar' - no problem.

  • Request 1 inserts.

  • Request 2 inserts.
Oops - duplicate user_name. If you want more technical detail, here's a good link.

This is a nasty, subtle bug. You won't see it in your unit tests. You probably won't see it under low loads. Depending on your application's design, you may never see it.

But, if you do see it, you'll see it under high request volumes. Once in a blue moon, maybe. You'll see it and tell yourself it's impossible, because you're validating uniqueness. And you may spend a lot of time looking for the answer, because the problem is not well known. The Rails API documentation doesn't mention it. The Agile book mentions it, but way down on page 371, well after validates_uniqueness_of has been introduced.

Why don't "they" fix it? Maybe because making it work correctly, and efficiently, across multiple database platforms, is far from trivial. But "they" should document the problem.

Should you stop using validates_uniqueness_of? Not necessarily. It'll do the right thing almost all the time, the error messages are handy, and it's a good way to show your intentions in your model.

But always back it up with constraints in the database. When that blue moon rises, better to have a save fail with a clumsy error message than store duplicate values in a column that should be unique.