我的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
答案 0 :(得分:2)
所以问题在于decrement!
skips validations。为了获得你想要的行为,你需要自己写一些类似的东西。一种简单的方法是验证balance
至少为0并使用update_attributes
来设置新的金额。您可以使用ActiveRecord::Base.transaction
和reload
来确保原子性。
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