Uniqueness validations in Rails
Validations on models in Rails are very powerful. They allow you to express certain properties that attributes of your model must possess, like uniqueness, numericality or presence, and Rails automatically takes care of enforcing them when a model is persisted. Moreover, Rails also provides customizable messages that tell you what went wrong when certain validations didn’t pass.
The uniqueness validator ensures that rows in your table are unique for certain columns. For instance, you can use it to ensure that your users’ emails are unique by setting a uniqueness validator on the attribute
The uniqueness validation is not atomic, therefore we can a typical synchronization issue that arises when you have concurrent processes.
Unique indexes on database
The way to solve this problem is to declare unique indexes on the unique column. This way, the second creation in the diagram would fail because of the constraint on the database. This new situation is shown in the diagram below.
Using Rails migrations, you can declare an index as follows.
class AddEmailIndexToUser def change # If you already have non-unique index on email, you will need # to remove it before you're able to add the unique index. add_index :users, :email, unique: true end end
The consistency_fail gem and Git
The consistency_fail gem analyses your models and identifies situations where you declared a uniqueness validator on an attribute without a corresponding database index. In order to identify such cases early on, I run the gem before each commit using a
pre-commit hook in Git. This forces me to fix the problem because I am confronted with it each time that I make a commit and the commit fails if consistency_fail identifies a problem.
.git/hooks/pre-commit file looks like this:
#!/bin/sh # Invoke consistency_fail for uniqueness index failures consistency_fail
Make sure it is executable.
chmod +x .git/hooks/pre-commit