我知道之前在Stack Overflow上已经提出了这个问题,但答案并没有以我能解释的方式为我做。我的一般方法受到this教程的启发。
我正在尝试做的是为友好用户创建一个非常简单的模型,通过一条记录在两端创建相同的友谊。
在db级别,我只有一个'friendships'表,它只有一个user_id,一个friend_id和一个is_pending布尔列。
在user.rb中,我将关系定义为:
has_many :friendships
has_many :friends, through: :friendships
在friendship.rb中,我将关系定义为:
belongs_to :user
belongs_to :friend, :class_name => 'User'
如果我添加友谊,我可以按如下方式访问:
> a = User.first
> b = User.last
> Friendship.new(a.id, b.id)
> a.friends
=> #<User b>
这是完美的,但我想要的是也能够像其他方向一样:
> b.friends
不幸的是,通过定义它的关系,我获得了一个空集合。运行的SQL显示它正在搜索user_id = b.id.如何指定它还应搜索friend_id = b.id?
答案 0 :(得分:2)
也许这个:
friendship.rb
belongs_to :friend_one, :foreign_key => :user_id
belongs_to :friend_two, :foreign_key => :friendship_id
和
user.rb
has_many :friendship_ones, :class_name => 'Friendship', :foreign_key => :friendship_id
has_many :friend_ones, through: :friendship_ones
has_many :friendship_twos, :class_name => 'Friendship', :foreign_key => :user_id
has_many :friend_twos, through: :friendship_twos
def friends
friend_ones + friend_twos
end
你有两个查询来查找朋友,但这是一个简单的数据模型,你只需要调用@ user.friends来查找实例。
如果你加载了两个friend_ones和friend_twos关联,那么就可以顺利加载。
答案 1 :(得分:2)
这也可以通过单个has_many :through
关联和一些查询来实现:
# 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
这可能不是所有方法中最简单的方法,但肯定是最干燥的方法。它不使用任何回调,其他记录和关联,并且使关联方法保持完整,包括隐式关联创建:
user.friends << new_friend
答案 2 :(得分:1)
本文介绍如何建立互惠关系:Bi-directional relationships in Rails
它显示了如何使用after_create
和after_destroy
插入模拟互惠关系的其他关系。这样,您的联接表中的记录就会翻倍,但您可以灵活地使用a.friends
和b.friends
,并确保两者都正确包含。< / p>
使其适用于您的模型:
class Person < ActiveRecord::Base
has_many :friendships, :dependent => :destroy
has_many :friends, :through => :friendships, :source => :person
end
class Friendship < ActiveRecord::Base
belongs_to :person, :foreign_key => :friend_id
after_create do |p|
if !Friendship.find(:first, :conditions => { :friend_id => p.person_id })
Friendship.create!(:person_id => p.friend_id, :friend_id => p.person_id)
end
end
after_update do |p|
reciprocal = Friendship.find(:first, :conditions => { :friend_id => p.person_id })
reciprocal.is_pending = self.is_pending unless reciprocal.nil?
end
after_destroy do |p|
reciprocal = Friendship.find(:first, :conditions => { :friend_id => p.person_id })
reciprocal.destroy unless reciprocal.nil?
end
end
我已经在一些项目上成功使用了这种方法,而且方便性非常棒!