Rails what's difference in unique index and validates_uniqueness_of

Here are the difference between unique index and validates_uniqueness_of

This is a patch to enable ActiveRecord to identify db-generated errors for unique constraint violations. For example, it makes the following work without declaring a validates_uniqueness_of:

create_table "users" do |t|
  t.string   "email",   null: false
end
add_index "users", ["email"], unique: true

class User < ActiveRecord::Base
end

User.create!(email: '[email protected]')
u = User.create(email: '[email protected]')
u.errors[:email]
=> "has already been taken"

The benefits are speed, ease of use, and completeness --

Speed

With this approach you don't need to do a db lookup to check for uniqueness when saving (which can sometimes be quite slow when the index is missed -- https://rails.lighthouseapp.com/projects/8994/tickets/2503-validate... ). If you really care about validating uniqueness you're going to have to use database constraints anyway so the database will validate uniqueness no matter what and this approach removes an extra query. Checking the index twice isn't a problem for the DB (it's cached the 2nd time around), but saving a DB round-trip from the application is a big win.

Ease of use

Given that you have to have db constraints for true uniqueness anyway, this approach will let everything just happen automatically once the db constraints are in place. You can still use validates_uniqueness_of if you want to.

Completeness

validates_uniqueness_of has always been a bit of a hack -- it can't handle race conditions properly and results in exceptions that must be handled using somewhat redundant error handling logic. (See "Concurrency and integrity" section in http://api.rubyonrails.org/classes/ActiveRecord/Validations/ClassMe...)

validates_uniqueness_of is not sufficient to ensure the uniqueness of a value. The reason for this is that in production, multiple worker processes can cause race conditions:

  1. Two concurrent requests try to create a user with the same name (and we want user names to be unique)

  2. The requests are accepted on the server by two worker processes who will now process them in parallel

  3. Both requests scan the users table and see that the name is available

  4. Both requests pass validation and create a user with the seemingly available name

For more clear understanding please check this

If you create a unique index for a column it means you’re guaranteed the table won’t have more than one row with the same value for that column. Using only validates_uniqueness_of validation in your model isn’t enough to enforce uniqueness because there can be concurrent users trying to create the same data.

Imagine that two users tries to register an account with the same email where you have added validates_uniqueness_of :email in your user model. If they hit the “Sign up” button at the same time, Rails will look in the user table for that email and respond back that everything is fine and that it’s ok to save the record to the table. Rails will then save the two records to the user table with the same email and now you have a really shitty problem to deal with.

To avoid this you need to create a unique constraint at the database level as well:

class CreateUsers < ActiveRecord::Migration
  def change
    create_table :users do |t|
      t.string :email
      ...
    end
    
    add_index :users, :email, unique: true
  end
end

So by creating the index_users_on_email unique index you get two very nice benefits. Data integrity and good performance because unique indexes tends to be very fast.

If you put unique: true in your posts table for user_id then it will not allow to enter duplicate records with same user_id.


As for the uniqueness goes,

Uniqueness validates that the attribute's value is unique right before the object gets saved. It does not create a uniqueness constraint in the database, so it may happen that two different database connections create two records with the same value for a column that you intend to be unique. To avoid that, you must create a unique index on both columns in your database.

Also, if you just have validates_uniqueness_of at model level then you would be restricted to insert duplicate records from rails side BUT not at the database level. SQL inject queries through dbconsole would insert duplicate records without any problem.

When you say that you created a foreign key with index on "user_id" in "posts" table then by default rails only creates an index on it and NOT a unique index. If you have 1-M relationship then there is no point in unique index in your case.

If you had unique: true in your posts table for "user_id" then there is no way that duplicate records with same "user_id" would go through


Db Unique index and i quote from this SO question is:

Unique Index in a database is an index on that column that also enforces the constraint that you cannot have two equal values in that column in two different rows

While ROR uniqueness validation should do the same but from application level, meaning that the following scenario could rarely but easily happen:

  • User A submits form
  • Rails checks database for existing ID for User A- none found
  • User B submits form
  • Rails checks database for existing ID for User B- none found
  • Rails Saves user A record
  • Rails saves user B record

Which happened to me a month ago and got advise to solve it using DB unique index in this SO question

By the way this workaround is well documented in Rails:

The best way to work around this problem is to add a unique index to the database table using ActiveRecord::ConnectionAdapters::SchemaStatements#add_index. In the rare case that a race condition occurs, the database will guarantee the field’s uniqueness