Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ruby on Rails ActiveRecord "has_many :through" uniqueness validation

Currently I insert a new relationship by everytime checking, if it doesn't exist:

unless Relationship.exists?(:entry_id => entry.id, :tag_id => tag.id)

How could I implement such validation inside the Relationship model, so that it wouldn't allow to have more than one relationship between the same entry and tag?

like image 532
krn Avatar asked Feb 26 '11 20:02

krn


2 Answers

class Relationship < ActiveRecord::Base
  belongs_to :entry
  belongs_to :tag
  validates :tag_id, :uniqueness => { :scope => :entry_id }
end
like image 165
fl00r Avatar answered Nov 04 '22 08:11

fl00r


Assuming your models look something like this:

class Entry < ActiveRecord::Base
  has_many :relationships
  has_many :tags, :through => :relationships
end

class Tag < ActiveRecord::Base
  has_many :relationships
  has_many :entries, :through => :relationships
end

class Relationship < ActiveRecord::Base
  belongs_to :entry
  belongs_to :tag
end

You could add a unique validation to your Relationship join model:

validates_uniqueness_of :tag_id, :scope => :entry_id

The validates_uniqueness_of method will ensure that the Relationship doesn't already exist, and the :scope option will scope the match to the given column. The SQL generated by rails via this validation will look like:

SELECT `relationships`.id
FROM `relationships`
WHERE (`relationships`.`tag_id` = <tag id> AND `relationships`.`entry_id` = <entry id>)
LIMIT 1

(which you'll notice is essentially the same SQL generated by your explicit use of Relationship.exists?(:entry_id => entry.id, :tag_id => tag.id)), and if a record is found, validation will fail.

As well, as with any case where you want to validate uniqueness, ensure that you have a unique key on tag_id, entry_id in your relationships table. See this article and the "Concurrency and integrity" of the API page I linked above for more info.

like image 9
Daniel Vandersluis Avatar answered Nov 04 '22 06:11

Daniel Vandersluis