Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails what's difference in unique index and validates_uniqueness_of

Firstly, can anyone explain how unique index works in databases?

Suppose I have a User model with a name column and I add a unique index on it but in the model (user.rb) I just have a presence validator on the name field.

So now when I'm trying to create two users with same name, I get PGError

duplicate key value violates unique constraint "index_users_on_name"

So It looks to me like the unique index working same as uniqueness validator(?)

If so then what about foreign keys?

Lets say I have Post model with belongs_to :user association with User has_many :posts. And a foreign key user_id in the posts table with unique index. Then multiple posts cannot have a same user_id.

Can someone explain how unique index works?

I'm on Rails 4 with Ruby 2.0.0.

like image 261
mrudult Avatar asked Apr 20 '14 20:04

mrudult


People also ask

What is the purpose of an unique index?

Unique indexes are indexes that help maintain data integrity by ensuring that no rows of data in a table have identical key values. When you create a unique index for an existing table with data, values in the columns or expressions that comprise the index key are checked for uniqueness.

Do Postgres indexes have to be unique?

Non-unique indexes are not explicitly specified in PostgreSQL. An index that isn't unique simply has the option to have duplicate value, yet that fact isn't shown in description of database tables. For example, the author column could be indexed but have multiple occurrences of a given value within that column.

What is a unique index PSQL?

PostgreSQL automatically creates a unique index when a unique constraint or primary key is defined for a table. The index covers the columns that make up the primary key or unique constraint (a multicolumn index, if appropriate), and is the mechanism that enforces the constraint.

Is index column unique?

A unique index ensures the index key columns do not contain any duplicate values. A unique index may consist of one or many columns. If a unique index has one column, the values in this column will be unique. In case the unique index has multiple columns, the combination of values in these columns is unique.


3 Answers

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.

like image 63
LHH Avatar answered Oct 14 '22 04:10

LHH


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

like image 21
Nimir Avatar answered Oct 14 '22 03:10

Nimir


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

like image 33
Kirti Thorat Avatar answered Oct 14 '22 04:10

Kirti Thorat