问题描述
我知道之前在 Stack Overflow 上有人问过这个问题,但答案并没有以我可以解释的方式为我解决.我的一般方法的灵感来自本教程.
I know this question has been asked before on Stack Overflow, but the answers aren't doing it for me in ways I can explain. My general approach was inspired by this tutorial.
我想要做的是创建一个非常简单的模型来为用户添加好友,通过一条记录在两端创建等效的好友关系.
What I'm trying to do, is create a really simple model for friending users that creates an equivalent friendship on both ends with a single record.
在数据库级别,我只有一个friendships"表,其中只有一个 user_id、一个friend_id 和一个 is_pending 布尔列.
At the db level, I just have a 'friendships' table that just has a user_id, a friend_id, and an is_pending boolean column.
在 user.rb 中,我将关系定义为:
In user.rb I've defined the relationship as:
has_many :friendships
has_many :friends, through: :friendships
在friendship.rb 中,我将这种关系定义为:
In friendship.rb, I've defined the relationship as:
belongs_to :user
belongs_to :friend, :class_name => 'User'
如果我添加好友,我可以访问如下:
If I add a friendship, I can access as follows:
> a = User.first
> b = User.last
> Friendship.new(a.id, b.id)
> a.friends
=> #<User b>
那很完美,但我想要的是也能像这样朝另一个方向发展:
That's perfect, but what I want is to also be able to go in the other direction like so:
> b.friends
不幸的是,按照原样定义的关系,我得到了一个空集合.运行的 SQL 显示它正在搜索 user_id = b.id.我如何指定它也应该搜索friend_id = b.id?
Unfortunately, with the relationship defined as it is, I get an empty collection. The SQL that runs shows that it's searching for user_id = b.id. How do I specify that it should also search for friend_id = b.id?
推荐答案
这也可以通过单个 has_many :through
关联和一些查询来实现:
This is also achievable with a single has_many :through
association and some query fiddling:
# app/models/friendship.rb
class Friendship < ApplicationRecord
belongs_to :user
belongs_to :friend, class_name: 'User'
end
# app/models/user.rb
class User < ApplicationRecord
has_many :friendships,
->(user) { FriendshipsQuery.both_ways(user_id: user.id) },
inverse_of: :user,
dependent: :destroy
has_many :friends,
->(user) { UsersQuery.friends(user_id: user.id, scope: true) },
through: :friendships
end
# app/queries/friendships_query.rb
module FriendshipsQuery
extend self
def both_ways(user_id:)
relation.unscope(where: :user_id)
.where(user_id: user_id)
.or(relation.where(friend_id: user_id))
end
private
def relation
@relation ||= Friendship.all
end
end
# app/queries/users_query.rb
module UsersQuery
extend self
def friends(user_id:, scope: false)
query = relation.joins(sql(scope: scope)).where.not(id: user_id)
query.where(friendships: { user_id: user_id })
.or(query.where(friendships: { friend_id: user_id }))
end
private
def relation
@relation ||= User.all
end
def sql(scope: false)
if scope
<<~SQL
OR users.id = friendships.user_id
SQL
else
<<~SQL
INNER JOIN friendships
ON users.id = friendships.friend_id
OR users.id = friendships.user_id
SQL
end
end
end
它可能不是最简单的,但肯定是最干燥的.它不使用任何回调、额外的记录和关联,并保持关联方法不变,包括隐式关联创建:
It may not be the simplest of them all but it's certainly the DRYest. It does not use any callbacks, additional records and associations, and keeps the association methods intact, including implicit association creation:
user.friends << new_friend
这篇关于如何在 Rails 中建立相互友谊的模型的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!