铁路4中的相互关系

时间:2016-02-13 05:03:23

标签: ruby-on-rails ruby rails-activerecord rails-models

我试图允许我的应用中的用户通过朋友请求彼此成为共同的朋友,我对这些关系如何工作有点困惑...当一个用户创建友谊并被其接受时另一方面,我希望两个用户都可以看到这种友谊(显然)。

我想实现一个允许我做类似于以下内容的实现:

user1 friend requests user2
user2 accepts
user1.friends now contains user2
user2.friends now contains user1

这是我到目前为止的内容,但我已经阅读了有关嵌套has_many的一些奇怪的事情:通过关系

class User < ActiveRecord::Base
 has_many :friendships
 has_many :friends, :class_name => "User", :through => :friendships
end

class Friendship < ActiveRecord::Base
 has_many :users, :limit => 2
end

这是一个可行的实施吗?如果没有,我可以改变/改进什么?如果可能的话,我想避免代表一种关系的2行。

2 个答案:

答案 0 :(得分:3)

您正在寻找的是一个has_and_belongs_to_many关系,但是对于同一个表,有点像Many-to-many relationship with the same model in rails?详细描述的那样。但是,既然您希望关系是双向的(“我的朋友也都是我的朋友”),您有两种选择:

  1. 使用单个连接表,每一行连接两个user_ids,但为每个友谊插入两行。

    # no need for extra columns on User
    class User < ActiveRecord::Base
      has_many :friendships
      has_many :friends, through: :friendships
    end
    
    # t.belongs_to :user; t.belongs_to :friend
    class Friendship < ActiveRecord::Base
      belongs_to :user
      belongs_to :friend, class_name: "User"
    end
    
    u1 = User.create!
    u2 = User.create!
    u3 = User.create!
    
    # make users 1 and 2 friends
    u1.friendships.create(friend: u2)
    u2.friendships.create(friend: u1)        
    
    # make users 2 and 3 friends
    u2.friendships.create(friend: u3)
    u3.friendships.create(friend: u2)        
    
    # and now, u1.friends returns [u1],
    # u2.friends returns [u1, u3] and
    # u3.friends returns [u2].
    
  2. 使用单个记录,但很难找到你的朋友:

    # no need for extra columns on User
    class User < ActiveRecord::Base
      has_many :friendships_as_a, class_name: "Friendship", foreign_key: :user_a_id
      has_many :friendships_as_b, class_name: "Friendship", foreign_key: :user_b_id
    
      def friends
        User.where(id: friendships_as_a.pluck(:user_b_id) +          friendships_as_b.pluck(:user_a_id))
      end
    end
    
    # t.belongs_to :user_a; t.belongs_to :user_b
    class Friendship < ActiveRecord::Base
      belongs_to :user_a, class_name: "User"
      belongs_to :user_b, class_name: "User"
    end
    
  3. 这不是最干净的方法,但我认为你会发现在设置这样的时候(使用非规范化表)并没有真正特别干净的方法。选项1是一个更安全的赌注。您还可以通过自动生成每个友谊的镜像条目,使用SQL视图来达到中间位置。

    编辑:迁移&amp; API中的用法

    根据下面的OP评论,要完全使用选项1,以下是您需要做的事情:

    rails g migration CreateFriendships
    

    将该文件编辑为:

    class CreateFriendships < ActiveRecord::Migration
      create_table :friendships do |t|
        t.belongs_to :user
        t.belongs_to :friend
        t.timestamps
      end
    end
    

    创建友谊模型:

    class Friendship < ActiveRecord::Base
      belongs_to :user
      belongs_to :friend, class_name: "User"
    end
    

    然后在您的用户模型上:

    class User < ActiveRecord::Base
      # ...
    
      has_many :friendships
      has_many :friends, through: :friendships, class_name: 'User'
    
      # ...
    end
    

    在您的API中,请说一个新的FriendshipsController:

    class FriendshipsController < ApplicationController
      def create
        friend = User.find(params[:friend_id])
    
        User.transaction do # ensure both steps happen, or neither happen
          Friendship.create!(user: current_user, friend: friend)
          Friendship.create!(user: friend, friend: current_user)
        end
      end
    end
    

    您的路线(config/routes.rb):

    resource :friendships, only: [:create]
    

    请求看起来像:

    POST /friendships?friend_id=42
    

    然后,只要您想查找用户与谁是朋友,就可以参考current_user.friends

答案 1 :(得分:1)

您使用has_many :through

#app/models/user.rb
class User < ActiveRecord::Base
  has_many :friendships
  has_many :friends, through: :friendships, -> { where(status: "accepted") }
end

#app/models/friendship.rb
class Friendship < ActiveRecord::Base
  belongs_to :user
  belongs_to :friend, class_name: "User"

  enum status: [:pending, :accepted]
  validates :user, uniqueness: { scope: :friend, message: "You can only add a friend once" }

  def decline
    self.destroy
  end

  def accept
    self.update status: "approved"
  end
end

以上是self-referential join,允许以下内容:

@user   = User.find params[:id]
@friend = User.find params[:friend_id]

@user.friends << @friend

-

这将为用户添加新的friendship,其默认status设置为pending。设置了@user.friends关联,以便 accepted个好友来自通话。

因此,您将能够执行以下操作:

#config/routes.rb
resources :users do
  resources :friendships, only: [:index, :destroy, :update], path_names: { destroy: "remove", update: "accept" }
end

#app/controllers/Frienships_controller.rb
class FriendshipsController < ApplicationController
  def index
    @user       = User.find params[:user_id]
    @friendship = @user.friendships
  end

  def update
    @user       = User.find params[:user_id]
    @friendship = @user.friendships.find params[:id]
    @friendship.accept
  end

  def destroy
    @user       = User.find params[:user_id]
    @friendship = @user.friendships.find params[:id]
    @friendship.decline
  end
end

#app/views/friendships/index.html.erb
<%= @friendships.pending.each do |friendship| %>
  <%= link_to "Accept", user_friendships_path(user, friendship), method: :put %>
  <%= link_to "Decline", user_friendships_path(user, friendship), method: :delete %>
<% end %>