3

I have seen the railscast on self-referential relationships here: http://railscasts.com/episodes/163-self-referential-association

I have built upon this in that I've included a 'status' field on friendships so that friendships must be requested and accepted. 'status' is a boolean -- false for not responded to yet, true for accepted.

My problem is in coming up with a method for finding a friendship object given the current_user (I'm using Devise) and another user.

Here is what is available to me:

current_user.friends              # lists people you have friended
current_user.inverse_friends      # lists people who have friended you
current_user.friendships          # lists friendships you've created
current_user.inverse_friendships  # lists friendships someone else has created with you
friendship.friend                 # returns friend in a friendship

I am looking to get a method similar to the following so that I can check the status of friendships easily:

current_user.friendships.with(user2).status

Here's my code: user.rb

has_many :friendships
has_many :friends, :through => :friendships
has_many :inverse_friendships, :class_name => "Friendship", :foreign_key => "friend_id"
has_many :inverse_friends, :through => :inverse_friendships, :source => :user

friendship.rb

belongs_to :user
belongs_to :friend, :class_name => "User"

While I'm at it -- to display a user's friends I have to display both "current_user.friends" and "current_user.inverse_friends" -- is there any way to just be able to call "current_user.friends" and have it be a join of the two?

Adam
  • 131
  • 9
  • Why not just make a friendship a direction-neutral relationship which is either accepted or declined (status boolean)? Once a friendship has been accepted does it really matter which person originally "inititated" it? – Andrew Jul 18 '11 at 16:25
  • @Andrew that would be ideal -- I do want to keep who invited who however so the sender can cancel/the receiver can accept/decline. I would love to know how to do that but it doesn't answer the main question unfortunately. – Adam Jul 18 '11 at 16:29
  • Did you figure it out? Did my answer help? If not - can you post the solution here as an answer? – Taryn East Nov 21 '11 at 14:06

1 Answers1

0

You can pass conditions to a given association thus:

has_many :friends, :class_name => 'User', :conditions => 'accepted IS TRUE AND (user = #{self.send(:id)} || friend = #{self.send(:id)})"'

Note: we use send so it doesn't evaluate the attributes until it tries to fetch them out.

If you really want the ".with(user2)" syntax, then you might be able to do it via a named_scope eg

Class Friendship
  named_scope :with, lambda { |user_id|
      { :conditions => { :accepted => true, :friend_id => user_id } }
    }
end

should allow:

user1.friendships.with(user2.id)

Note: code not tested - you may have to bugfix...

Taryn East
  • 27,486
  • 9
  • 86
  • 108
  • I am having trouble when running your (simplified) association: `has_many :friends, :class_name => "User", :conditions => '(user_id = #{self.send(:id)} || friend_id = #{self.send(:id)})'`. The error I get when doing user.friends is: `ActiveRecord::StatementInvalid: SQLite3::SQLException: no such column: user_id: SELECT "users".* FROM "users" WHERE ("users".id = 3 AND ((user_id = 3 || friend_id = 3)))` – Adam Jul 18 '11 at 18:55
  • Like I said - not tested. You'll need to update it to fit your own implementation and bugfix it. I'm guessing that your user-model uses different column-names for these... alternatively, you'll have to specify that it's "friendships".user_id etc – Taryn East Jul 25 '11 at 09:59