在十进制列中强制实施最小余额0

时间:2016-03-22 04:12:33

标签: ruby-on-rails

我的User模型has_one Account,其中包含balance列。

观察:

> alice = User.first
 => #<User id: 1, charge_fee: nil, created_at: "2016-03-19 20:15:14", updated_at: "2016-03-19 20:15:14"> 
> bob = User.second
 => #<User id: 2, charge_fee: nil, created_at: "2016-03-19 20:15:18", updated_at: "2016-03-19 20:15:18"> 
> alice.account.balance.to_s
 => "50.0" 
> bob.account.balance.to_s
 => "50.0" 

当一个用户将钱转移到另一个用户时,发件人的余额会减少,接收者会增加:

> alice.account.transfer(to: bob.account, amount:25)
 => nil 
> alice.account.balance.to_s
 => "25.0" 
> bob.account.balance.to_s
 => "75.0" 

系统是完美的,除了用户可以转移比他们更多的钱,他们的余额变为负面:

> alice.account.transfer(to:bob.account, amount:50)
 => nil 
> alice.account.balance.to_s
 => "-25.0" 
> bob.account.balance.to_s
 => "125.0" 

如果Account.decrement!('balance', amount)低于account.balance,我如何强制执行命令0

可以找到此应用程序的完整来源here。实际的increment!decrement!发生here,这里是摘录:

Account.transaction do
  Transaction.create(transaction)
  transaction[:to].increment!('balance', transaction[:amount])
  transaction[:from].decrement!('balance', transaction[:amount])

  Transaction.create(fee_transaction) if transaction[:transfer]
end

2 个答案:

答案 0 :(得分:2)

所以问题在于decrement! skips validations。为了获得你想要的行为,你需要自己写一些类似的东西。一种简单的方法是验证balance至少为0并使用update_attributes来设置新的金额。您可以使用ActiveRecord::Base.transactionreload来确保原子性。

class Account < ActiveRecord::Base
  validates :balance, numericality: { greater_than_or_equal_to: 0 }

  def withdraw!(amount)
    transaction do
      reload
      update_attributes!(balance: balance - amount)
    end
  end
end

如果您有理由不使用Rails的验证(也许有时人们可以透支他们的支票帐户),您也可以直接在withdraw!方法中进行检查。然后,如果您愿意,仍然可以使用decrement!

class Account < ActiveRecord::Base
  def withdraw!(amount)
    transaction do
      reload
      raise 'Insufficient funds' unless balance >= amount
      decrement!('balance', amount)
    end
  end
end

根据其他情况,您可以跳过reload以避免额外的数据库查询,只要您确信自己拥有最新记录。

答案 1 :(得分:0)

如果失败'平衡不能为负'       交易[:from] .balance&lt; BigDecimal('0')&amp;&amp;       交易[:从] .META? == false