rails 4 中的相互关系
Mutual relationships in rails 4
我试图让我的应用程序中的用户通过好友请求成为彼此的共同好友,但我对这种关系的运作方式有点困惑...当一个用户建立友谊并且被对方接受,我希望双方用户都能看到这种友谊(很明显)。
我想实现一个允许我执行类似于以下操作的实现:
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
这是一个可行的实施方案吗?如果没有,我能change/improve做什么?如果可能,我想避免 2 行代表一种关系。
您正在寻找的是 has_and_belongs_to_many 关系,但与 table 相同,有点像 Many-to-many relationship with the same model in rails? 所详细描述的。但是,由于您希望关系是双向的 ("my friends are all also friends with me"),您有两个选择:
使用单个联接 table,每行链接两个 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].
使用单个记录,但 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
这不是最干净的方法,但我认为您会发现在这样设置时(使用非规范化 table)并没有真正特别干净的方法。选项 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
任何时候你想知道一个用户和谁是朋友
你会使用 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 %>
我试图让我的应用程序中的用户通过好友请求成为彼此的共同好友,但我对这种关系的运作方式有点困惑...当一个用户建立友谊并且被对方接受,我希望双方用户都能看到这种友谊(很明显)。
我想实现一个允许我执行类似于以下操作的实现:
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
这是一个可行的实施方案吗?如果没有,我能change/improve做什么?如果可能,我想避免 2 行代表一种关系。
您正在寻找的是 has_and_belongs_to_many 关系,但与 table 相同,有点像 Many-to-many relationship with the same model in rails? 所详细描述的。但是,由于您希望关系是双向的 ("my friends are all also friends with me"),您有两个选择:
使用单个联接 table,每行链接两个 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].
使用单个记录,但 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
这不是最干净的方法,但我认为您会发现在这样设置时(使用非规范化 table)并没有真正特别干净的方法。选项 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
任何时候你想知道一个用户和谁是朋友
你会使用 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 %>