有没有更好的方法来防止一个IP地址执行两次操作?

时间:2015-04-30 15:24:31

标签: ruby-on-rails ruby arrays database performance

我有一个模型Item可以通过创建Vote进行投票。 Vote有一个属性:ip,用于记录用户的IP地址。如果一个Item的Votes数组包含一个特定的ip,则该ip被阻止创建另一个投票。

模型:

class Thing < ActiveRecord::Base
  has_many :votes
end

class Vote < ActiveRecord::Base
  belongs_to :thing
end

items_controller:

def vote
  @item = Item.find(params[:id])
  if (!@item.votes.map(&:ip).include? request.remote_ip)
    Vote.create!(ip: request.remote_ip, item_id: @item.id)
  end
end

这个模型现在还不慢。但我担心当我开始获得数百万票时会发生什么。在一个例子中,我正在获取当前ip尚未投票的每个项目的数组。随着选票数量的增加,这将是一个疯狂的记录数量。有谁知道更好的方法吗?

我有一个想法如下:

1。)用用户的IP加载一系列投票:@votes = Vote.where(ip: request.remote_ip)

2。)创建与这些投票相关联的项目(@items)的数组

3。)&#34;减去&#34;来自@items的{​​{1}}来创建新数组Item.all

4.)检查@unvoted_items是否包含当前项目

这会是一个更有效的模型吗?谁能想到更好的一个?

4 个答案:

答案 0 :(得分:1)

你可以做得更简单:

@item.votes.where(ip: request.remote_ip).exists?

只要您的投票表上有[:item_id, :ip]的索引,这就会很快。

此外,您应该考虑使索引成为唯一的(或者您将容易受到竞争条件的影响)。根据使用模式,您可以选择依赖唯一索引:

begin
  @item.votes.create!(ip: request.remote_ip, item_id: @item.id)
rescue ActiveRecord::RecordNotUnique
  # take whatever action is required upon a duplicate entry
end

如果大多数插入成功,那么这将节省几乎总是成功的检查。

答案 1 :(得分:1)

如果你有多个rails服务器,我认为你会提到“数百万”,这个机制将不起作用。更好的方法是使用唯一索引强制对数据库中的IP进行限制。启动会快得多。

答案 2 :(得分:1)

以下将在数据库中进行搜索,而无需全部检索;

if Vote.where(item_id: @item.id, ip: request.remote_ip).exists?

end

答案 3 :(得分:1)

所有当前答案都需要向控制器添加条件逻辑。 Rails具有验证唯一数据库记录的内置方法。只需在模型上添加唯一性验证:

validates_uniqueness_of :ip, message: 'has already voted'

现在在您的控制器中,您只需要处理两种可能的结果:有效或无效。这是一个例子:

def create
  @vote = Vote.new(item_id: @item.id, ip: request.remote_ip)

  if @vote.save
    redirect_to @vote
  else
    render 'new'
  end
end

如果有人试图投票两次,@vote.errors.messages将如下所示:

{ ip: ['has already voted'] }

这些错误通常以表格形式显示。

有关ActiveRecord验证的更多信息,请参阅http://guides.rubyonrails.org/active_record_validations.html