Preventing Duplicates in a Rails API

When validations are just not enough

Kenny Marks
5 min readDec 21, 2020

I recently came across an interesting issue where I was seeing duplicate users entries with the same usernames on a Rails API. I was surprised by this because had added a validation on my User model to prevent this:

I thought that with the uniqueness validation on the username attribute it would prevent any duplicates from forming within my database. So I had to figure out how was this happening! In this blog post I am going to cover why this was happening, and how to prevent it so that you can avoid this too!

Why was I getting duplicates on new User entries even with a uniqueness validation?

The uniqueness validation will always compare new entries with entries that already exist within a database. For example, when I subbmited sign up from to add a user with a username of “testing”, Rails will then check its database for other users who have this username. If there are none that match Rails will add this to our API on the other hand, on the otherhand if it finds a match it will not save it.

Since my user was being saved it must have passed the uniqueness validation and therefore could not have existed prior. So why was I still getting duplicates? Well it turns out that if two users are saved at the exact same time they will be saved faster than the validation can check the new entries.

I realized that there were a few ways this error could occur, First I were to click the submit button rapidly or if I were to open two windows and click submit at virtually the same time. Now that I could recreate this error it was time add a fix to solve it. Thankfully Rails does have a way to prevent this from happening!

How do you prevent this error?

To prevent this error you can add an index to your model and make it unique. In Rails an index exists to make it easier for the database to look up a specific piece of information. In particular, we want to make sure we add an index to that particular attribute in this case ‘usernames’.

There are several ways to add an index to a model in Rails. Firstly if we are creating a new model we can simply use the generate feature to create the migration file or resources and include the index on the attribute we want to be unique. For example to create a ‘User’ model with simply the attributes username and password we would run the command:

$ rails g resource User username:string:uniq:index password_digest

This will create the User migration, model, controller, and routes. Looking at the migration file we will see the following now:

At the bottom we see add_index :users, :username, unique: true. To add this to an existing project which already has a user model simply run:

$ rails g migration AddIndexToUsers username:string:uniq:index

This will create the following migration:

This migration will also add the column ‘username’ to the migration which already exists before running the migration simply delete the add_column line so that the migration file looks like this:

Next run the migration:

$ rails db:migrate

And when the table is created we will see:

Or if it is updated:

The table has been created and an index has been added to our user’s table (if this migration were being run on an existing database it would appear a bit different). This will ensure that when a new user entry is added to the database even multiple entries are submitted very quickly. If duplicate user entries try to be saved at the same time we will now see this in our terminal:

The first entry ‘begins’ and runs the create function on a user found in the controller, it then checks that there is no index for the username ‘testing ’and then ‘commits’ (saves) it to the database. On the second entry attempt we see that the database begins to add the duplicate user but then runs a rollback once it realizes it has already created an index for the username ‘testing’. Finally, ActiveRecord provides a description of the Error “duplicate key value violates unique constraint ‘index_users_on_username.’”

Conclusion

By adding an index to a model this will ensure that duplicates cannot be added when new entries to a database. While this serves as a way to prevent duplicate entries on pre existing users as well I still like to add a validation on models (for example: ‘validates :username, uniqueness: {message: “This username has already been taken}’) where appropriate to ensure uniqueness because it will act as an earlier check on pre-existing entries in a database. With this you can now prevent your database from adding duplicates on new netries! Happy Coding!

--

--

Kenny Marks

A full stack developer with an interest in Cybersecurity, International Relations, and Gaming.