ActiveRecord uniqueness validations cannot be used on their own – we must use database level constraints to guard against race conditions. Given that we are guarding against duplication at the database level, should we also introduce uniqueness constraints in ActiveRecord models, and if so, under what circumstances?
All of the examples use the
locations table. The
code column in this table has a unique index. Let’s start off by adding the following ActiveRecord validation to our model:
When we save a change to an existing record, we see something like the following:
Note the presence of
Location Exists. This extra database call to ensure
code is unique occurs before every update, even when
code was not one of the columns updated! The performance for this extra database call is somewhat severe. 1000 updates, average of 10 runs produced:
- 3.104 seconds without ActiveRecord uniqueness validation
- 4.796 seconds with ActiveRecord uniqueness validation
For those keeping score, that’s 55% slower.
Let’s say we never change
code in our application – we could change the validation to
validates_uniqueness_of :code, on: :create so we only see the extra database call once during a record’s lifespan. We could also use
ActiveModel::Dirty to run the validation only when
code has changed with
validates_uniqueness_of :code, if: :code_changed?.
We’ve demonstrated model level uniqueness validations can be expensive, so why bother when we can rescue from
As Erik Michaels-Ober noted in his presentation at Baruco 2014, Writing Fast Ruby, using exceptions for control flow in Ruby is over 10x slower than if/else. Benchmarking 1000 failed creates, (also average of 10 runs) produced:
- 1.681 seconds for failed creates rescuing from
- 1.525 seconds for failed creates using
In addition to the 10% speed increase, the ActiveRecord validation also has the advantage of making it possible to see all validation errors at once in
model.errors. A RecordNotUnique exception is only thrown when we hit the database so the user would never see a uniquness-related error if their inputs failed any other validation, leaving them with the frustrating experience of making corrections in two steps instead of one.