防止快速按钮粉碎

时间:2014-03-28 16:29:17

标签: ruby-on-rails

现在,如果我非常快速地粉碎这个“卖出”按钮,以便html没有时间更新,我可以不断出售相同的项目并每次获得积分,我该如何防止这种情况?

用户模型的方法:

has_many :drivers


def withdraw(amount)
    balance = self.credit

    if balance >= amount
        new_balance = balance - amount
        self.update credit: new_balance
    true
    else 
        false
    end     
end

def deposit(amount)
    balance = self.credit
    balance += amount
    self.update credit: balance 
end

def purchase(package)
    cost = package.cost
    ActiveRecord::Base.transaction do
      self.withdraw(cost)
      package.update user_id: self.id
    end
end

def sell(package)
  cost = package.cost
  ActiveRecord::Base.transaction do
    self.deposit(cost)
    package.update user_id: nil
  end
end

使用买入/卖出按钮查看:

<% unless @driver.owned? %>
    <%= button_to "Buy", purchase_driver_path %>
<% else %>
    <%= button_to "Sell", sell_driver_path, method: :delete %>
<% end %>

我的控制器

class DriversController < ApplicationController

    def show
        @user = current_user
        @driver = Driver.find(params[:id])
    end

    def purchase
        @driver = Driver.find(params[:id])
        @user = current_user

        if @user.purchase(@driver)
            flash[:succes] = "Purchase succesful!"
        else
            flash[:error] = "Error"         
        end     
        render "show"
    end

    def sell
        @driver = Driver.find(params[:id])
        @user = current_user

        if @user.sell(@driver)
            flash[:succes] = "Sell succesful!"
        else
            flash[:error] = "Error"         
        end     
        render "show"

    end
end

谢谢!

3 个答案:

答案 0 :(得分:1)

最简单的方法可能是在sell方法中插入一个保护条款(目前,package当前属于用户并不重要。)

def sell(package)
  return unless package.user == self
  ... # your original method here
end

这样,除非满足初始条件,否则该方法将不执行任何操作。

答案 1 :(得分:1)

您应该在模型层上处理此问题,以便您知道其他任何代码都无法利用此竞争条件。

做两件事:

1)为重要的无效状态逻辑添加模型验证,例如

class Driver
  validate :validate_not_previously_purchased, on: :purchase

  def validate_not_previously_purchased
    if user_id && user_id_change[0] != nil
      errors.add(:user_id, 'a user has already purchased this product')
    end
  end
end

2)在锁定交易中使用验证

class User
  ...
  def purchase(package)
    ActiveRecord::Base.transaction(lock: true) do
      package.user_id = self.id
      package.save(context: :purchase)
      self.withdraw(package.cost)
    end
  end
end

交易&amp;如果验证失败,验证会使其回滚,并且锁定确保多个用户在最后一次胜利的竞争条件下不会“购买”包。

您还需要考虑以相同方式验证用户信用的业务逻辑,以便如果购买交易没有足够的信用等,也会失败。

这是很好的机会,用于对代码的超级关键部分进行单元测试。

最后,提醒一下保持锁中逻辑量尽可能小的警告。锁定既是数据库的主要特征,也是大型应用程序扩展问题的祸根。只要你把东西保持在快速锁定状态光(它现在是),它会很好。

答案 2 :(得分:0)

除了Zach的方法之外,您还可以添加一些客户端JavaScript以在点击后立即禁用该按钮。我必须在一个表单上传文档的应用程序中执行此操作,这使得向用户提供反馈的速度很慢(即,删除表单页面等)。

// CoffeeScript
$('.button-class').click (e) ->
  $(@).attr('diabled', 'disabled')

或者(这个恰好是JS ......抱歉,从两个文件中获取代码示例:)) -

$('.form-button').submit(function(e) {
    $(this).find('input[type=submit]').attr('disabled', 'disabled');
});

这两种方法对我有用,具体取决于我是否需要提交或单击处理程序中的操作。