0

我有一个用户模型,其中用户有另一个用户的友谊。友谊模型使用类名为 User 的朋友。一切似乎都在正常工作。但是,我认为我只是试图将功能修补在一起,而不是遵循最佳程序。

在我的控制器,友谊控制器中,我有它 current_user 可以添加朋友的地方。但是,我不希望他们添加同一个朋友两次。

user = current_user.id
friend = params[:friend_id]
temp_friendship = Friendship.where('(user_id = ? AND friend_id = ?) OR (user_id = ? AND friend_id = ?)', user,friend,friend,user)
if !temp_friendship.present?
  @friendship = current_user.friendships.build(:friend_id => params[:friend_id])
  if @friendship.save
    redirect_to current_user, :notice => "Added friend."
  else
    redirect_to current_user, :alert => "Unable to add friend."
  end
else
  redirect_to current_user, :alert => "Already a friend."
end

这段代码都很好用。但是,似乎我正在对数据库进行不必要的调用。有没有办法优化这个控制器调用,通过模型验证或类似的方法?

我试过这样做,但如果我已经启动了朋友,它只会返回验证错误。如果有人将我添加为朋友(其中friend_id 将是我的用户 ID),它不会引发任何错误。

validates_uniqueness_of :user_id, :scope => :friend_id
validates_uniqueness_of :friend_id, :scope => :user_id
4

3 回答 3

1

您无法针对性能进行优化

你在这里做的基本上是:

  1. 请求检查是否存在具有相同 ID 的记录
  2. 如果没有就写一条新记录

这听起来对你来说很熟悉,你决定尝试一种方法来实现它作为唯一性验证;但这不是解决方案,您实际上正在做的事情#validates_uniquess是:检查所有ID,然后保存。

在这一点上,你不能做得更好,你已经将问题减少到最小的步骤。因此,即使您可以将其转换为对称范围唯一性规则,它仍然会触发两个数据库查询(实际上,它会使用两个触发三个#validates_uniqueness_of)。

您可以优化易读性

在这一点上,您可以做几件事。这不是开玩笑:当您稍后必须快速阅读整个控制器时,它会节省时间,在您编写它之后。

首先,您的 temp_friendship 查询可以是范围和模型方法。那将是他们的位置,它可能会被证明是有用的。

其次,如果友谊存在,重定向可能是一个前置过滤器,这将使操作方式更加清晰:

class User < ActiveRecord::Base
  has_many :friends, through: :friendship

  scope :friend_with, ->( other ) do
    other = other.id if other.is_a?( User )
    where( '(friendships.user_id = users.id AND friendships.friend_id = ?) OR (friendships.user_id = ? AND friendships.friend_id = users.id)', other, other ).includes( :frienships )
  end

  def friend_with?( other )
    User.where( id: id ).friend_with( other ).any?
  end
end


class FriendshipsController < ApplicationController
  before_filter :check_friendship, only: :create

  def create
    @friendship = current_user.friendships.build( friend_id: params[:friend_id] )

    if @friendship.save
      redirect_to current_user, notice: 'Added friend.'
    else
      redirect_to current_user, alert: 'Unable to add friend.'
    end
  end

  private

  def check_friendship
    redirect_to( current_user, alert: 'Already a friend' ) if current_user.friend_with?( params[ :friend_id ] )
  end
end
于 2013-09-21T08:16:02.760 回答
1

这里的另一个选择是从 Rails 应用程序中删除验证并在数据库中强制执行唯一性。

这是非常规的,但消除了对额外查询的需要,并且如果数据库外部的应用程序强制执行的行内验证永远不会是一种方式,那么它是完全安全的。(因此,Rails 指南中有关使用数据库验证备份 ActiveRecord 验证的评论。

您将在模型的保存中添加一个救援步骤来处理 RDBMS 抛出的唯一性错误,并将它们视为验证失败。这里有关于拯救数据库错误的好信息:Rails 3 ignore Postgres unique constraint exception

我只是把它作为另一种选择,所以只需评估它,看看非常规代码的权衡对你来说是否值得。

我真的很想看到这种方法封装在 activerecord 中。也许我应该卷起袖子……

于 2013-09-21T08:35:56.743 回答
0

定义验证器可用于检查“A 是 B 的朋友”或“B 是 A 的朋友”作为“友谊已经存在”的情况。但是,数据库命中的总数可能不会下降 - 验证器只会对您已经编写的内容执行类似的检查。这可能仍然是一种更好的方法,因为它将逻辑移出您的控制器,但我不希望有任何性能改进。

于 2013-09-21T08:15:09.897 回答