How do you validate uniqueness of a pair of ids in Ruby on Rails?

Ruby on-RailsValidationModelUnique Constraint

Ruby on-Rails Problem Overview


Suppose the following DB migration in Ruby:

create_table :question_votes do |t|
t.integer :user_id
t.integer :question_id
t.integer :vote

  t.timestamps
end

Suppose further that I wish the rows in the DB contain unique (user_id, question_id) pairs. What is the right dust to put in the model to accomplish that?

validates_uniqueness_of :user_id, :question_id
seems to simply make rows unique by user id, and unique by question id, instead of unique by the pair.

Ruby on-Rails Solutions


Solution 1 - Ruby on-Rails

validates_uniqueness_of :user_id, :scope => [:question_id]

if you needed to include another column (or more), you can add that to the scope as well. Example:

validates_uniqueness_of :user_id, :scope => [:question_id, :some_third_column]

Solution 2 - Ruby on-Rails

If using mysql, you can do it in the database using a unique index. It's something like:

add_index :question_votes, [:question_id, :user_id], :unique => true

This is going to raise an exception when you try to save a doubled-up combination of question_id/user_id, so you'll have to experiment and figure out which exception to catch and handle.

Solution 3 - Ruby on-Rails

The best way is to use both, since rails isn't 100% reliable when uniqueness validation come thru.

You can use:

  validates :user_id, uniqueness: { scope: :question_id }

and to be 100% on the safe side, add this validation on your db (MySQL ex)

  add_index :question_votes, [:user_id, :question_id], unique: true

and then you can handle in your controller using:

  rescue ActiveRecord::RecordNotUnique

So now you are 100% secure that you won't have a duplicated value :)

Solution 4 - Ruby on-Rails

From RailsGuides. validates works too:

class QuestionVote < ActiveRecord::Base
  validates :user_id, :uniqueness => { :scope => :question_id }
end

Solution 5 - Ruby on-Rails

Except for writing your own validate method, the best you could do with validates_uniqueness_of is this:

validates_uniqueness_of :user_id, :scope => "question_id"

This will check that the user_id is unique within all rows with the same question_id as the record you are attempting to insert.

But that's not what you want.

I believe you're looking for the combination of :user_id and :question_id to be unique across the database.

In that case you need to do two things:

  1. Write your own validate method.
  2. Create a constraint in the database because there's still a chance that your app will process two records at the same time.

Solution 6 - Ruby on-Rails

When you are creating a new record, that doesn't work because the id of your parent model doesn't exist still at moment of validations.

This should to work for you.

class B < ActiveRecord::Base
  has_many :ab
  has_many :a, :through => :ab
end

class AB < ActiveRecord::Base
  belongs_to :b
  belongs_to :a
end

class A < ActiveRecord::Base
  has_many :ab
  has_many :b, :through => :ab

  after_validation :validate_uniqueness_b

  private
  def validate_uniqueness_b
    b_ids = ab.map(&:b_id)
    unless b_ids.uniq.length.eql? b_ids.length
      errors.add(:db, message: "no repeat b's")
    end
  end
end

In the above code I get all b_id of collection of parameters, then compare if the length between the unique values and obtained b_id are equals.
If are equals means that there are not repeat b_id.

Note: don't forget to add unique in your database's columns.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestiondfrankowView Question on Stackoverflow
Solution 1 - Ruby on-RailsDemiView Answer on Stackoverflow
Solution 2 - Ruby on-RailspakehaView Answer on Stackoverflow
Solution 3 - Ruby on-RailsdpedonezeView Answer on Stackoverflow
Solution 4 - Ruby on-RailsJonathan LinView Answer on Stackoverflow
Solution 5 - Ruby on-RailseggdropView Answer on Stackoverflow
Solution 6 - Ruby on-RailsFrancisco BalamView Answer on Stackoverflow