本文介绍了如何在 Rails 中建立相互友谊的模型的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我知道之前在 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

通过 gist.github.com

这篇关于如何在 Rails 中建立相互友谊的模型的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

09-05 03:37