method

validates_uniqueness_of

validates_uniqueness_of(*attr_names)
public

Validates whether the value of the specified attributes are unique across the system. Useful for making sure that only one user can be named "davidhh".

  class Person < ActiveRecord::Base
    validates_uniqueness_of :user_name, :scope => :account_id
  end

It can also validate whether the value of the specified attributes are unique based on multiple scope parameters. For example, making sure that a teacher can only be on the schedule once per semester for a particular class.

  class TeacherSchedule < ActiveRecord::Base
    validates_uniqueness_of :teacher_id, :scope => [:semester_id, :class_id]
  end

When the record is created, a check is performed to make sure that no record exists in the database with the given value for the specified attribute (that maps to a column). When the record is updated, the same check is made but disregarding the record itself.

Because this check is performed outside the database there is still a chance that duplicate values will be inserted in two parallel transactions. To guarantee against this you should create a unique index on the field. See add_index for more information.

Configuration options:

  • :message - Specifies a custom error message (default is: "has already been taken").
  • :scope - One or more columns by which to limit the scope of the uniqueness constraint.
  • :case_sensitive - Looks for an exact match. Ignored by non-text columns (false by default).
  • :allow_nil - If set to true, skips this validation if the attribute is nil (default is false).
  • :allow_blank - If set to true, skips this validation if the attribute is blank (default is false).
  • :if - Specifies a method, proc or string to call to determine if the validation should occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). The method, proc or string should return or evaluate to a true or false value.
  • :unless - Specifies a method, proc or string to call to determine if the validation should not occur (e.g. :unless => :skip_validation, or :unless => Proc.new { |user| user.signup_step <= 2 }). The method, proc or string should return or evaluate to a true or false value.

4Notes

Back it up with a unique index

rob-twf · Oct 24, 200810 thanks

As mentioned briefly above, as well as using this validation in your model you should ensure the underlying database table also has a unique index to avoid a {race condition}[http://en.wikipedia.org/wiki/Race_condition].

For example:

class User < ActiveRecord::Base
validates_uniqueness_of :login_name
end

The index can be specified in the migration for the User model using {add_index}[http://apidock.com/rails/ActiveRecord/ConnectionAdapters/SchemaStatements/add_index] like this:

add_index :users, :login_name, :unique => true

You do a similar thing when using the :scope option:

class Person < ActiveRecord::Base
validates_uniqueness_of :user_name, :scope => :account_id
end

Should have a migration like this:

add_index :people, [ :account_id, :user_name ], :unique => true

Note that both the attribute being validated (:user_name) and the attribute(s) used in the :scope (:account_id) must be part of the index.

For a clear and concise explanation of the potential for a race condition see {Hongli Lai's blog}[http://izumi.plan99.net/blog/index.php/2008/09/20/validates_uniqueness_of-does-not-guarantee-uniqueness/].

Does not work with polymorphic relations

szeryf · Apr 20, 20102 thanks

If you have polymorphic relations, e.g.:

class Bookmark < ActiveRecord::Base
belongs_to :thing, :polymorphic => true
belongs_to :owner, :polymorphic => true
end

and you want to ensure that a thing can bookmarked by an owner at most once, you can't do this:

validates_uniqueness_of :thing, :scope => :owner

Instead, you must use the real column names, e.g.:

validates_uniqueness_of :thing_id, :scope => [:thing_type, :owner_id, :owner_type]

multi scope to sql

RobinWu · Jun 3, 2009

====

validates_uniqueness_of :name, :scope => [:big_category_id, :small_category_id]

SELECT * FROM schedules WHERE (products.name = 'xxxx' AND products.big_category_id= 1 AND products.small_category_id = 1) LIMIT 1

Migration for uniqueness with existent data in DB

dani · Dec 6, 2013

I'm using sub-transaction to update existent records on DB. I use this approach to update the uniqueness field when it value dependent on another existent field without uniqueness restriction.

==== Migration for uniqueness with existent dependent data in DB

class AddUniquenessBarToFoo < ActiveRecord::Migration
class Foo < ActiveRecord::Base
end

def change

  add_column :foos, :bar, :string
  execute "ALTER TABLE foos ADD CONSTRAINT uk_foods_bar UNIQUE (bar)"    

  Foo.reset_column_information
  Foo.all.each do |f|
    begin
      #try get unique value in a new sub-transaction
      Foo.transaction(requires_new: true) do
        f.update_attributes!(:bar => "some ops. with another non-unique existent field to set this")
      end
    rescue ActiveRecord::StatementInvalid
       #We can't reuse a crashed transaction. New one.
       Foo.transaction(requires_new: true) do
        #Alternative unique value, if another error exist it's another
        #migration problem and then raise new error.
        f.update_attributes!(:bar => "some operation to set this-#{f.id}")
      end
    end
  end   
  change_column :foos, :bar, :string, :null => false

end
end

Be aware about performance that is transaction per record for big DB.