首页 文章

铁轨4中的相互关系

提问于
浏览
1

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

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

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 回答

  • 3

    您正在寻找的是has_and_belongs_to_many关系,但是对于同一个表,有点像Many-to-many relationship with the same model in rails?详细描述的那样 . 但是,由于您希望关系是双向的("my friends are all also friends with me"),因此您有两种选择:

    • 使用单个连接表,每个连接表链接两个user_id,但为每个友谊插入两行 .
    # 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].
    
    • 使用单个记录,但hackery找到你的朋友:
    # 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
    

    这不是最干净的方法,但我认为你会发现在设置这样的时候(使用非规范化表)并没有真正特别干净的方法 . 选项1是一个更安全的赌注 . 您还可以通过自动生成每个友谊的镜像条目,使用SQL视图来达到中间位置 .

    编辑: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

    你用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 %>
    

相关问题