203

I have a Release model with medium and country columns (among others). There should not be releases that share identical medium/country combinations.

How would I write this as a rails validation?

K M Rakibul Islam
  • 33,760
  • 12
  • 89
  • 110
Jackson Cunningham
  • 4,973
  • 3
  • 30
  • 80
  • 8
    Possible duplicate of [Rails: Validate uniqueness of multiple columns](http://stackoverflow.com/questions/4870961/rails-validate-uniqueness-of-multiple-columns) – Khoga Dec 22 '15 at 21:03

3 Answers3

346

You can use a uniqueness validation with the scope option.

Also, you should add a unique index to the DB to prevent new records from passing the validations when checked at the same time before being written:

class AddUniqueIndexToReleases < ActiveRecord::Migration
  def change
    add_index :releases, [:country, :medium], unique: true
  end
end



class Release < ActiveRecord::Base
  validates :country, uniqueness: { scope: :medium }
end
David Moles
  • 48,006
  • 27
  • 136
  • 235
tompave
  • 11,952
  • 7
  • 37
  • 63
  • +1 for the index, but -1 for the `unique` as it is not recognized. For that part I have used the answer below. – Aleks Jun 08 '16 at 11:01
  • 11
    Yes, sorry, the validation key should be `uniqueness`, not `unique`. See the linked documentation. Fixing the answer. – tompave Jun 08 '16 at 11:17
  • 1
    Hm, nice, thanks :) To repeat myself - putting the index brings the solution to the next level, and not just like other "coding" solutions that I have been running into, before finding this answer. +1 for that – Aleks Jun 08 '16 at 11:28
  • 3
    'Pass' an array to `scope:` to check uniqueness across more than two fields. – Dennis Hackethal Nov 16 '21 at 07:59
  • Would this work if we had a partial unique index? Lets say we define the unique index with "If medium is not null". We would have many pairs with "country" null. Are these treated as unique or not unique? To answer my own question: If you add allow_nil: true to validates as answered here it will work: https://stackoverflow.com/questions/18496223/possible-to-specify-unique-index-with-nulls-allowed-in-rails-activerecord – gurel_kaynak Oct 14 '22 at 07:45
131

All the above answers are missing how to validate the uniqueness of multiple attributes in a model. The code below intends to tell how to use multiple attributes in a scope.

validates :country, uniqueness: { scope: [:medium, :another_medium] }

It validates uniqueness of country in all rows with values of medium and another_medium.

Note: Don't forget to add an index on the above column, this insures fast retrieval and adds a DB level validation for unique records.

Update: For adding an index while creating table

t.index [:country, :medium, :another_medium], unique: true
Justin
  • 2,883
  • 1
  • 17
  • 25
Aamir
  • 16,329
  • 10
  • 59
  • 65
  • since the validates is based on 3 column the index table should too t.index [:country, :medium, :another_medium], unique: true – buncis Feb 10 '22 at 10:05
  • Your `t.index` taught me something. I thought Rails couldn't do multicolumn indexes. – pedz Sep 27 '22 at 14:35
53

You can pass a :scope parameter to your validator like this:

validates_uniqueness_of :medium, scope: :country

See the documentation for some more examples.

K M Rakibul Islam
  • 33,760
  • 12
  • 89
  • 110
  • 13
    @DennisBest It "works", but it doesn't protect against race conditions. If two clients make simultaneous requests, they could both pass validation if neither is committed to the database before the other is validated. You also need a database unique constraint as in tompave's answer. – soupdog Nov 18 '16 at 22:04