我有一个用户模型,其中用户拥有另一个用户的友谊。友谊模型使用具有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
答案 0 :(得分:1)
你在这里做的基本上是:
对你来说这听起来很熟悉,你决定尝试将其作为唯一性验证来实现;但这不是解决方案,你实际上正在完成#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的朋友'作为'友谊已经存在'的情况。但是,数据库命中总数可能不会下降 - 验证器只会对您已编写的内容执行类似的检查。它可能仍然是一种更好的方法,因为它会将逻辑移出控制器,但我不希望任何性能提升。