486

I would like to make a column unique in Ruby on Rails migration script. What is the best way to do it? Also is there a way to index a column in a table?

I would like to enforce unique columns in a database as opposed to just using :validate_uniqueness_of.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Tam
  • 11,872
  • 19
  • 69
  • 119

8 Answers8

787

The short answer for old versions of Rails (see other answers for Rails 4+):

add_index :table_name, :column_name, unique: true

To index multiple columns together, you pass an array of column names instead of a single column name,

add_index :table_name, [:column_name_a, :column_name_b], unique: true

If you get "index name... is too long", you can add name: "whatever" to the add_index method to make the name shorter.

For fine-grained control, there's a "execute" method that executes straight SQL.

That's it!

If you are doing this as a replacement for regular old model validations, check to see how it works. The error reporting to the user will likely not be as nice without model-level validations. You can always do both.

ndp
  • 21,546
  • 5
  • 36
  • 52
  • 38
    +1 for suggesting continuing to use the validates_uniqueness_of. The error handling is much cleaner using this method for the cost of a single indexed query I would suggest he does both – Steve Weet Sep 19 '09 at 22:08
  • 2
    I tried that it doesn't seem to work! I could insert two record with the column_name that I defined as unique! I'm using Rails 2.3.4 and MySql any ideas? – Tam Sep 20 '09 at 04:57
  • I used you second suggestion by using execute: execute "ALTER TABLE users ADD UNIQUE(email)" and it works! not sure why the first one didn't would be interested in knowing – Tam Sep 20 '09 at 05:19
  • I found that the composite index alone didn't present any nice errors, therefore validated uniqueness as well. Cheers! – Michael De Silva Jul 15 '11 at 12:21
  • @Tam - I found this comment about the same problem on apidock. Looks like you just need to add one more parameter to the method `:name => index_name` http://apidock.com/rails/ActiveRecord/ConnectionAdapters/SchemaStatements/add_index#note_info-953 – plainjimbo Mar 29 '12 at 21:32
  • If you want uniqueness, I believe you still have to add `:unique => true` as the last argument to `add_index` even if the second argument is an array. Details can be found at the same doc linked above: http://apidock.com/rails/ActiveRecord/ConnectionAdapters/SchemaStatements/add_index – Jason Swett May 03 '13 at 20:26
  • 1
    If you get an `indexed columns are not unique` error when trying to create a unique index, it might be because the data in the table already contains duplicates. Try removing the duplicate data and running the migration again. – Hartley Brody Oct 04 '13 at 23:18
  • What does `add_index :table_name, [:column_name_a, :column_name_b], :unique => true` do? Is the uniqueness enforced using two columns now? – Jwan622 Dec 19 '16 at 00:17
  • As a side note, don't forget to add validators to your models. eg: `class TableName < ApplicationRecord validates :column_name_a, presence: true, uniqueness: { scope: :column_name_b, message: 'column_name_a and column_name_b must be unique' } end` – Blaskovicz Jan 20 '17 at 21:35
  • Alert: Using an array of column names like `[:column_name_a, :column_name_b]` will create a composite index rather than indexing each column. – Kunal Kapadia Mar 06 '18 at 10:39
  • Following suggested, adding to`validates :column_name, uniqueness: true` to the model definition will result in a 422 response code rather than 500. Also returns a specific message. – NicoE Nov 03 '20 at 15:44
  • If you need to change a column anyway (like, decrease `limit`), you can go with: `change_column :table, :column, :type, limit: 191, unique: true`. – x-yuri Dec 16 '21 at 16:01
158

rails generate migration add_index_to_table_name column_name:uniq

or

rails generate migration add_column_name_to_table_name column_name:string:uniq:index

generates

class AddIndexToModerators < ActiveRecord::Migration
  def change
    add_column :moderators, :username, :string
    add_index :moderators, :username, unique: true
  end
end

If you're adding an index to an existing column, remove or comment the add_column line, or put in a check

add_column :moderators, :username, :string unless column_exists? :moderators, :username
go2null
  • 2,080
  • 1
  • 21
  • 17
d.danailov
  • 9,594
  • 4
  • 51
  • 36
101

If you are creating a new table, you can use the inline shortcut:

  def change
    create_table :posts do |t|
      t.string :title, null: false, index: { unique: true }
      t.timestamps
    end
  end
Pioz
  • 6,051
  • 4
  • 48
  • 67
57

Since this hasn't been mentioned yet but answers the question I had when I found this page, you can also specify that an index should be unique when adding it via t.references or t.belongs_to:

create_table :accounts do |t|
  t.references :user, index: { unique: true } # or t.belongs_to

  # other columns...
end

(as of at least Rails 4.2.7)

Steve Grossi
  • 2,765
  • 1
  • 22
  • 26
20

I'm using Rails 5 and the above answers work great; here's another way that also worked for me (the table name is :people and the column name is :email_address)

class AddIndexToEmailAddress < ActiveRecord::Migration[5.0]
  def change
    change_table :people do |t|
      t.index :email_address, unique: true
    end
  end
end
Nicholas Nelson
  • 211
  • 2
  • 3
1

You might want to add name for the unique key as many times the default unique_key name by rails can be too long for which the DB can throw the error.

To add name for your index just use the name: option. The migration query might look something like this -

add_index :table_name, [:column_name_a, :column_name_b, ... :column_name_n], unique: true, name: 'my_custom_index_name'

More info - http://apidock.com/rails/ActiveRecord/ConnectionAdapters/SchemaStatements/add_index

Swaps
  • 1,450
  • 24
  • 31
1
add_index :table_name, :column_name, unique: true

To index multiple columns together, you pass an array of column names instead of a single column name.

Peter Haddad
  • 78,874
  • 25
  • 140
  • 134
murali
  • 11
  • 2
-1

If you have missed to add unique to DB column, just add this validation in model to check if the field is unique:

class Person < ActiveRecord::Base
  validates_uniqueness_of :user_name
end

refer here Above is for testing purpose only, please add index by changing DB column as suggested by @Nate

please refer this with index for more information

Ravistm
  • 2,163
  • 25
  • 25
  • 3
    I would not recommend just adding the validation without a corresponding index. The better option is to clean up any existing duplicates and then add the index. Otherwise you risk invalidating existing data (which will cause any updates to those rows to fail), and you could still end up with duplicates if you have any code that skips Rails validations. (e.g., when running an update_all, or direct SQL inserts) – Nate Feb 04 '19 at 20:11
  • 1
    This is good enough to show a nice error message but not to enforce integrity of your data. See https://thoughtbot.com/blog/the-perils-of-uniqueness-validations for an explanation. – Pascal Nov 12 '21 at 16:23