在rails中实现友谊模型的最佳方式

时间:2013-10-12 21:52:55

标签: ruby-on-rails model-associations

我想在我的应用中实现用户的朋友系统,所以我发现rails空间解决方案非常好,我想在the Friendships table中创建两行:第一行发件人邀请,第二个是接收者

用户之间的关系设置为has_many关联,如下所示:

has_many :friendships
has_many :friends, :through => :friendships, :conditions => "status = 'accepted'"

接受用户为朋友的方法是这样的:

# Accept a friend request.
def self.accept(user, friend)
    transaction do
        accepted_at = Time.now
        accept_one_side(user, friend, accepted_at)
        accept_one_side(friend, user, accepted_at)
    end
end 

accept_one_side()方法是:

# Update the db with one side of an accepted friendship request.
def self.accept_one_side(user, friend, accepted_at)
    request = find_by_user_id_and_friend_id(user, friend)
    request.status = 'accepted'
    request.accepted_at = accepted_at
    request.save!
end

这样做的好处是我们可以执行一个请求来获取双方的所有朋友(用户是发送邀请的人或朋友是谁发送的)

但我认为这有不利之处,例如在现实中有500个朋友,友谊表将包含“500X2 = 1000”行

第二个解决方案是与has_many through进行反向关联,如RailsCast #163 Self-Referential Association中所述:

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

但是如果你想为一个用户获取所有朋友,你应该使用两个请求:

user.friends
user.inverse_friends
如果你有一个庞大的友谊表,那么这不是最好的方式...

我想知道的是上述两种方法中哪种方法最好,那么有一种优化方法吗?如果还有另一种超级方法,我将感激不尽

1 个答案:

答案 0 :(得分:4)

我更喜欢在朋友之间需要两个连接的版本,每个方向一个。原因与您提到的相同:它允许对用户的朋友进行更多类似Rails的查询。

此外,我认为为友谊请求(一个方向)和现有友谊(两个方向)设置不同的表会更清楚

由于你在中间有一个友谊模型,我建议使用回调的魔力。如果你定义了一些回调,那么你必须只需要为连接的一端带蛋糕,回调应该能够创建(或删除)匹配的补充。

# in friendship_requests
after_save :created_friendship

def accept
  update_attributes(:status  => 'accepted')
end

private
  def created_friendship
    sender.friends << receiver  if status_changed? && status == 'accepted'
  end


# in user.rb
has_and_belongs_to_many :friends, after_add:    :create_complement_friendship,
                                  after_remove: :remove_complement_friendship

private
  def create_complement_friendship(friend)
    friend.friends << self  unless friend.friends.include?(self)
  end

  def remove_complement_friendship(friend)
    friend.friends.delete(self)
  end

这只是第一个想法,确实缺少一些验证器和回调......