2014-03-28 115 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'); 
}); 

這兩種方法對我來說這取決於我是否需要行動工作過在提交或點擊處理程序中。