在检查时优化建议以查看当前用户是否已成为朋友

时间:2013-09-21 01:20:25

标签: ruby-on-rails ruby-on-rails-3 validation optimization

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

在我的控制器中,友谊控制器,我有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

3 个答案:

答案 0 :(得分:1)

您无法优化演出

你在这里做的基本上是:

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

    对你来说这听起来很熟悉,你决定尝试将其作为唯一性验证来实现;但这不是解决方案,你实际上正在完成#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
    

答案 1 :(得分:1)

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

这是非常规的,但不需要额外的查询,并且如果由数据库以外的应用程序执行的行内验证永远的方式是完全安全的。 (因此Rails指南中关于使用数据库验证备份ActiveRecord验证的评论。

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

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

我真的希望看到这种方法封装在activerecord中。也许我应该卷起袖子......

答案 2 :(得分:0)

custom validator可以用来检查'A是B的朋友'还是'B是A的朋友'作为'友谊已经存在'的情况。但是,数据库命中总数可能不会下降 - 验证器只会对您已编写的内容执行类似的检查。它可能仍然是一种更好的方法,因为它会将逻辑移出控制器,但我不希望任何性能提升。