【问题标题】:Optimize suggestions on check to see if a current user is a friend already or not优化检查建议以查看当前用户是否已经是朋友
【发布时间】:2013-09-21 01:20:25
【问题描述】:

我有一个用户模型,其中用户与另一个用户有朋友关系。友谊模型使用类名为 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

【问题讨论】:

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


    【解决方案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
    

    【讨论】:

    • 感谢您提出的重写建议。这看起来确实更简洁,可以用于其他验证,比如如果他们已经是朋友,甚至不显示“添加朋友”按钮。不重复代码总是理想的。谢谢!
    【解决方案2】:

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

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

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

    我只是将此作为另一种选择,因此只需评估它以查看非常规代码的折衷对您来说是否值得。

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

    【讨论】:

    • 其实,someone tried to patch activerecord for this。像往常一样,它被埋没,没有提及任何拒绝它的充分理由。可悲的是,作者似乎没有将其提取为 gem(这是在您的补丁被接受之前收集用户和可见性的好方法)。由于它是从灯塔导入的,因此没有 github diff,而是 patch has been posted on gist as comment。祝你好运!
    • 感谢您的建议。我确实喜欢这种方法,并且会考虑修改我的迁移。
    • @OlivierElMekki 感谢您提供信息。
    【解决方案3】:

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

    【讨论】:

      猜你喜欢
      • 2012-10-10
      • 1970-01-01
      • 1970-01-01
      • 2013-03-31
      • 2018-03-24
      • 1970-01-01
      • 2015-05-05
      • 2015-01-31
      • 1970-01-01
      相关资源
      最近更新 更多