Rails: validate uniqueness of two columns (together)

88,537

Solution 1

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

Solution 2

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

Solution 3

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

validates_uniqueness_of :medium, scope: :country

See the documentation for some more examples.

Share:
88,537

Related videos on Youtube

Jackson Cunningham
Author by

Jackson Cunningham

Updated on July 27, 2022

Comments

  • Jackson Cunningham
    Jackson Cunningham almost 2 years

    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?

  • Aleks
    Aleks almost 8 years
    +1 for the index, but -1 for the unique as it is not recognized. For that part I have used the answer below.
  • tompave
    tompave almost 8 years
    Yes, sorry, the validation key should be uniqueness, not unique. See the linked documentation. Fixing the answer.
  • Aleks
    Aleks almost 8 years
    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
  • soupdog
    soupdog over 7 years
    @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.
  • Dennis Hackethal
    Dennis Hackethal over 2 years
    'Pass' an array to scope: to check uniqueness across more than two fields.
  • buncis
    buncis about 2 years
    since the validates is based on 3 column the index table should too t.index [:country, :medium, :another_medium], unique: true