views:

82

answers:

1

I'm working on an application that models friendships between users.

class User
  has_many :friendships
  has_many :friends, 
           :through => :friendships,
           :conditions => "status = #{Friendship::FULL}"
end
class Friendship
  belongs_to :user
  belongs_to :friend, :class_name => "User", :foreign_key => "friend_id"
end

When two users become friends, two instances of the friendship class are created, one for each direction of the friendship. This is necessary because different permissions can be set in each direction, and also for ease of lookups so we always search by the user_id without creating a second index and needing to double up searches.

Is it possible, using either find or a named scope to pull up a friend along with the counter-friendship? I'd like to do this because I'm allowing users to edit friendship permissions which are stored in their own leg of the friendship, and thus when I'm displaying a friend's info to the user I need to check the opposite leg to see how permissions are set. (As an aside, does this seem like a logical way to do things? Either you store into the opposite side, or you have to consult the opposite side before display, both kind of unpleasant.)

I've managed to whip up a batch of SQL that does the job:

def self.secure_friends(user_id)
  User.find_by_sql("SELECT u.*, cf.permissions
                    FROM users u 
                    INNER JOIN friendships f ON u.id = f.friend_id 
                    INNER JOIN friendships cf ON u.id = cf.user_id AND cf.friend_id = #{user_id} 
                    WHERE ((f.user_id = #{user_id}) AND (f.status = #{Friendship::FULL}))")
end

The function returns all of a user's friends names and ids, along with the permissions. However, this really doesn't seem ideal, and means I have to access the permissions by calling friend.permissions, where permissions isn't actually a member of friend but is just merged in as an attribute by find_by_sql. All in all, I'd really rather be doing this with a named_scope or a find call, but unfortunately, every attempt I've made to use that approach has resulted in errors as Rails chokes on having the same Friendship join table getting joined to the query twice.

+2  A: 

I'm not sure how much you want to do, and how much you'd allow a plugin to do -- but I've used this has_many_friends plugin with great success: has_many_friends plugin at Github

You can look at the source and be inspired... Just need a Friendship model that has things like:

belongs_to :friendshipped_by_me,   :foreign_key => "user_id",   :class_name => "User"
belongs_to :friendshipped_for_me,  :foreign_key => "friend_id", :class_name => "User"

and then later, in your user class, have:

has_many :friends_by_me,
         :through => :friendships_by_me,
         :source => :friendshipped_for_me

Or, just

/app/models/user

has_many_friends

Later:

@current_user.friends.each{|friend| friend.annoy!}
Jesse Wolgamott