Establishing a friend relationship with another user with a join model (user_id, friend_id). Normally this would result in one-way relationships (Jack is a friend of Jill but Jill is not a friend of Jack). To get around this I want to have an inverted entry in the join model table (not the nicest approach but I see no reason why it cannot work) so I set up my models like this:
class User < ActiveRecord::Base has_many :user_friends, :dependent => :destroy has_many :friends, :through => :user_friends, :order => :first_name end
class UserFriend < ActiveRecord::Base attr_accessor :final
belongs_to :user belongs_to :friend, :foreign_key => :friend_id, :class_name => “User” validates_presence_of :user, :friend
Make the relationship bidirectional
after_save :duplicate_inverted after_destroy :destroy_inverted
protected
# Create an inverted version of this record (unless one already exists)
def duplicate_inverted
return true if self.final
unless UserFriend.exists? :user_id => friend_id, :friend_id => user_id
self.class.create :user_id => friend_id, :friend_id => user_id, :final => true
end
end
# Remove the inverted version of this object
def destroy_inverted
if UserFriend.exists? :user_id => friend_id, :friend_id => user_id
self.class.destroy :user_id => friend_id, :friend_id => user_id
end
end
end
The UserFriend#final method exists so the iteration only happens once.
What happens, though, when I run this:
u1 = User.find 1 u2 = User.find 2 u1.friends.clear u1.friends << u2
User Columns (0.001914) SHOW FIELDS FROM users
User Load (0.000986) SELECT * FROM users
WHERE (users
.id
= 1)
User Load (0.000980) SELECT * FROM users
WHERE (users
.id
= 2)
User Load (0.001556) SELECT users
.* FROM users
INNER JOIN user_friends ON users.id = user_friends.friend_id WHERE ((user_friends
.user_id = 1)) ORDER BY first_name
SQL (0.000288) BEGIN
UserFriend Columns (0.000853) SHOW FIELDS FROM user_friends
User Load (0.000990) SELECT * FROM users
WHERE (users
.id
= 1)
User Load (0.000960) SELECT * FROM users
WHERE (users
.id
= 2)
*** UserFriend Create (0.000443) INSERT INTO user_friends
(updated_at
, user_id
, friend_id
, created_at
) VALUES(‘2008-06-11 15:04:46’, 1, 2, ‘2008-06-11 15:04:46’)
UserFriend Exists (0.000477) SELECT user_friends
.id FROM user_friends
WHERE (user_friends
.user_id
= 2 AND user_friends
.friend_id
= 1) LIMIT 1
User Load (0.000993) SELECT * FROM users
WHERE (users
.id
= 1)
User Load (0.000967) SELECT * FROM users
WHERE (users
.id
= 2)
*** UserFriend Create (0.000396) INSERT INTO user_friends
(updated_at
, user_id
, friend_id
, created_at
) VALUES(‘2008-06-11 15:04:46’, 1, 2, ‘2008-06-11 15:04:46’)
SQL (0.020969) COMMIT
I’ve put *** in front of the two important rows. The first creates the requested object, the second creates the inverted one. However, the second insert has user_id and friend_id set to the same values as the first insertion?
I tested this by replacing unless UserFriend.exists? :user_id => friend_id, :friend_id => user_id self.class.create :user_id => friend_id, :friend_id => user_id, :final => true end
with unless UserFriend.exists? :user_id => friend_id, :friend_id => user_id self.class.create :user_id => 5, :friend_id => 5, :final => true end
and the results were exactly the same. This leads me to believe there is some sort of with_scope or something going one which is appending those values to my create call but I don’t know how to find out whether or not this is the case.
Can anyone shine some light on this behaviour?
Cheers, Morgan Grubb.