Firyn
Firyn

Reputation: 312

Rails 3 Two Fields Uniqueness Validation when combined

I have a model Friendship where :friend_id is also a foreign key as :user_id

friendships
id | user_id | friend_id

I'm aware that validating the uniqueness of the two fields would be something like this

validates :user_id, :uniqueness => {:scope => :friend_id}

But is there a way to validate

user_id = 1, friend_id = 3
user_id = 3, friend_id = 1 

so that only user_id = 1, friend_id = 3 is stored?

Upvotes: 2

Views: 1494

Answers (6)

Michael Durrant
Michael Durrant

Reputation: 96604

One More!

def unique_relationship?
    self.class.where("(user_id = ? and friend_id = ?) or 
                      (user_id = ? and friend_id = ?)", 
                      user_id, friend_id, friend_id, user_id).empty?
end

Upvotes: 1

Michael Durrant
Michael Durrant

Reputation: 96604

Reading the question more closely I now think that it's really

validates :friend_id, :uniqueness => {:scope => :user_id}

Upvotes: 2

Michael Durrant
Michael Durrant

Reputation: 96604

validate :relationship_uniqueness

def relationship_uniqueness

  existing_record = Friendship.find(:first, :conditions => ["user_id = ? AND friend_id = ?", user_id, friend_id])
  unless existing_record.blank?
    errors.add(:user_id, "has already been saved for this relationship")
  end

end

Upvotes: 1

Matthew Rudy
Matthew Rudy

Reputation: 16844

This is hard problem.

How do you model a symmetric relationship?

Last time I did this I decided that in fact it is not symmetric. For an idea of how to model this look at Disapora's Contact class

class Contact < ActiveRecord::Base
  belongs_to :user
  belongs_to :person
  validates :person, :presence => true
  validates_presence_of :user
  validates_uniqueness_of :person_id, :scope => :user_id
end

You'll note they don't do anything special to ensure uniqueness.

What they do instead is carefully consider each point of the interaction that creates or breaks a friendship. We need to be careful to wrap them in transactions.

Basically, when a user sends a "friend request"

  1. We create a Contact from the "user" to the "friend" with a status of "pending"
  2. We create a Contact from the "friend" to the "user" with a status of "requested"

When the "friend" accepts this "request"

  1. Both Contacts are set to "accepted"

If the "friend" chooses to break the friendship

  1. Both Contacts are deleted

Each of these actions need to be done in a transaction.

As long as these transactions are one correctly we should never have a one-way friendship.

Upvotes: 1

Michael Durrant
Michael Durrant

Reputation: 96604

I would try:

validate :unique_relationship

def unique_relationship
  (user_id.to_s + friend_id.to_s).uniq
end

Upvotes: 0

Firyn
Firyn

Reputation: 312

So I thought of a way to work around this, but it's very messy

friendships
id
1

friend_lists
id | friendship_id | user_id
1  |       1       |   1
2  |       1       |   3

This way, I will have to make sure one friendship_id can only be entered twice into friend_lists, and do

validates :friendship_id, :uniqueness => {:scope => :user_id}

Upvotes: 0

Related Questions