I have implemented a friendship system, which happens to be
polymorphic, but that is not really relevant.
This handles invitations and the status of the invitation as well
(open, accepted, rejected).
It requires a table called friends (ignore the ..._type fields if not
polymorphic)...
CREATE TABLE friends (
id serial NOT NULL,
inviter_id integer,
inviter_type character varying(255),
invitee_id integer,
invitee_type character varying(255),
status integer DEFAULT 0,
created_at timestamp without time zone,
updated_at timestamp without time zone
);
Here is what would go into the User model....
this_class_name= 'User'
has_many :invites,
:class_name => "Friend",
:as => :inviter,
:order => 'created_at DESC',
:dependent => :destroy
has_many :outgoing_invites,
:class_name => "Friend",
:as => :inviter,
:conditions => "friends.status = 0",
:order => "created_at DESC"
has_many :accepted_invites,
:class_name => this_class_name,
:through => :invites,
:source => :invitee,
:source_type => this_class_name,
:conditions => "friends.status = 1"
has_many :invites_to,
:class_name => this_class_name,
:through => :invites,
:source => :invitee,
:source_type => this_class_name,
:conditions => "friends.status = 0"
has_many :rejected_invites,
:class_name => this_class_name,
:through => :invites,
:source => :invitee,
:source_type => this_class_name,
:conditions => "friends.status = -1"
has_many :rejections,
:class_name => "Friend",
:as => :invitee,
:conditions => "friends.status = -1",
:order => "created_at DESC"
has_many :invitations,
:class_name => "Friend",
:as => :invitee,
:order => 'created_at DESC',
:dependent => :destroy
has_many :incoming_invitations,
:class_name => "Friend",
:as => :invitee,
:conditions => "friends.status = 0",
:order => "created_at DESC"
has_many :accepted_invitations,
:class_name => this_class_name,
:through => :invitations,
:source_type => this_class_name,
:source => :inviter,
:conditions => "friends.status = 1"
has_many :invitations_from,
:class_name => this_class_name,
:through => :incoming_invitations,
:source_type => this_class_name,
:source => :inviter
has_many :rejected_invitations,
:class_name => this_class_name,
:through => :invitations,
:source => :inviter,
:source_type => this_class_name,
:conditions => "friends.status = -1"
An inefficient method to get all friends is...
# finds friends regardless of who was inviter or invitee, and
status is accepted
def friends
accepted_invites + accepted_invitations
end
A more efficient (untested) method is...
def friends
find_by_sql('SELECT p.* FROM users p, friends f WHERE f.status =
1 AND ( (f.invitee_id = #{id} AND p.id = f.inviter_id) OR
(f.inviter_id = #{id} AND p.id = f.invitee_id) )')
end
The advantages of this method are that only one entry per friendship
is required in the friends table (rather than two each way).
I'll be blogging in far more detail on this soon in my blog...
http://blog.wolfman.com
Hope that helps