【问题标题】:Is there a better way to prevent one ip address from performing an action twice?有没有更好的方法来防止一个 IP 地址执行两次操作?
【发布时间】:2015-07-10 09:21:28
【问题描述】:

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

型号:

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 尚未投票的每个 Item 的数组。随着投票数量的增加,这将是一个疯狂的记录数量。有谁知道更好的方法吗?

我的一个想法如下:

1.) 使用用户的 ip 加载一个投票数组:@votes = Vote.where(ip: request.remote_ip)

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

3.) 从Item.all 中“减去”@items 以创建一个新数组@unvoted_items

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

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

【问题讨论】:

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


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

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

    【讨论】:

      【解决方案2】:

      如果您有多个 Rails 服务器,我假设您会使用这种机制,因为您提到了“数百万”。更好的方法是使用唯一索引强制限制您的数据库中的 IP。开机会快很多。

      【讨论】:

        【解决方案3】:

        以下将在数据库中进行搜索而不检索所有这些;:

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

        【讨论】:

        • !@item.votes.where(item_id: @item.id, ip: request.remote_ip).exists? 会因为某种原因变慢吗?
        • 是的,这个是一样的,你问题中的问题是这部分@item.votes.map(&:ip),因为你将加载所有投票及其所有参数
        【解决方案4】:

        所有当前的答案都需要向您的控制器添加条件逻辑。 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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-01-29
          • 2011-02-21
          • 2023-01-30
          • 2017-04-14
          相关资源
          最近更新 更多