2014-03-28 101 views
1

现在,如果我非常快速地粉碎这个“销售”按钮,以便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 

谢谢!

回答

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 

如果验证失败,交易&验证会使其回滚,并且锁确保在最后一次获胜的情况下,包装将不会被多个用户在竞赛状态下“买入”。

你也需要做各地以同样的方式验证用户信用的业务逻辑的一些思考,所以,如果他们没有足够的信用购买交易也将失败等

这是一个很好单元测试代码的这个超级关键部分的机会。

最后,一个警告的话,以保持尽可能小的锁在逻辑上完成的数量。锁定既是数据库的主要特征,也是大型应用程序扩展问题的祸根。只要你把东西保持在快速锁定的状态(当前就是这样),就可以了。

+0

您好,感谢您的回复,但可能只是人们解释“!user_id_change [0] == nil”测试的内容?然后,“package.update user_id:self.id”然后是“package.user_id = self.id package.save(context::purchase)” – manis

+0

!user_id_change [0] == nil应该是什么区别user_id_change [0]!=无 – Woahdae

+0

这是说“如果ID从一个ID更改为另一个,这是无效的”。 user_id_change是一个动态定义的方法,它返回[from-value,to-value]的数组。请参阅“活动记录脏对象”文档(在我的手机上,或者我会链接到它) – Woahdae

1

最简单的方法可能是在您的sell方法中插入警戒条款(目前,package目前属于用户并不重要)。

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

这样,除非满足初始条件,否则该方法什么也不做。

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'); 
}); 

这两种方法对我来说这取决于我是否需要行动工作过在提交或点击处理程序中。