ThiagoPradi.net

Software with French Fries and a Big Coke.

Database Tip 1: Why Validate_uniqueness_of Is Broken

This is a series of posts where I will be talking about some database tips for long time railers.

In the past, I’ve been working on many rails projects, including big ones, that relies only on Rails validations for checking all the Data Integrety before the model is saved in the Database.

For most of validations, this makes sense. but validate_uniqueness_of is terrible broken, and could lead you to a number of problems.

Imagine the following scenario:

You have your model user, which have a unique e-mail, like this:

1
2
3
4
class User < ActiveRecord::Base
validate_presence_of :email
validate_uniqueness_of :email
end

So, in your production environment, you have configured to use unicorn + ngnix, with 4 workers.

In your production environment, the user creating is wrapped into a transaction with more business logic. So, here is the problem:

  1. Worker 1 starts a user creation trasaction
  2. Worker 2 starts a user creation trasaction
  3. Worker 1 makes the query to see if this e-mail is already taken, which return false, because the user wasn’t persisted in the database yet.
  4. Worker 2 makes the query to see if this e-mail is already taken, which return false, because the user wasn’t persisted in the database yet.
  5. Worker 1 finish the transaction, inserting the user.
  6. Worker 2 finishes the trasaction, inserting a user with duplicated e-mail.

This could be easily solved using a unique index in the database, which raises a exception if a duplicated data is entered. The index could be created in a migration, using the following code:

1
add_index :users, :email, :unique => true

For more about unique indexes, check this article: Mysql Unique Indexes and this PostgreSQL Unique indexes

Protip: be nice to your users, and rescue from the exception when this occurs, putting a nice message, like the following code:

1
2
3
4
5
6
7
8
9
10
11
12
class UsersController < ActionController::Base
def create
User.transaction do
# do user stuff
end
rescue ActiveRecord::StatementInvalid => e
flash[:error] = "Something bad happens"
redirect_to new_user_path
end
end

New Blog

Hi Guys! This is my first post using my new blog engine. This blog is powered by Octopress

Some events that happenned this year gave me some extra motivation to start writing again, like getting my english skills on track again (it’s so worst now) and experiences in my new job / college.

Hope you guys like it!

Thanks,

Thiago