What is with_lock? how it work
Ans: If two users read a value from a record and then update it simultaneously, one of the values has to “win” and data integrity will be compromised. This is dangerous especially when dealing with money or inventory stocks.
For example:
User has a card balance = 10
User opens browser A and fill out 5 USD to buy
User opens browser B and fill out 10 USD to buy
User hits “Buy” button of both browsers at the same time
Request A reads card balance=10
Request B reads card balance=10
Request A updates balance -= 5 (balance now is 10-5=5)
Request B still has the instance card balance=10 (even though request A already decremented it)
Request B updates balance -= 10 (balance now is 10-10=0)
Final balance is now 0
User was able to purchase 15 USD when the initial balance was only 10 USD. This is race condition potentially at its worst case!
Rails doesn’t do locking when loading a row from the database by default. If the same row of data from a table is loaded by two different processes and then updated at different times, race conditions like the scenario mentioned above will occur.
coups = Coupon.all.sample
coups.lock! # no other users can read this coupon, they have to wait until the lock is released
coups.save! # lock is released, other users can now read this
We can either use lock! or with_lock. lock! is cool in itself, but with_lock is even cooler. You can wrap a critical code with a with_lock method and live happily. The with_lock method accepts a block which is executed within a transaction and the instance is reloaded with lock: true.
# Get a coupon
@coupon = Coupon.all.sample
@coupon.with_lock do
@coupon.balance += 5
@coupon.save!
end
Under the hood, this is what happens within a with_lock block:
1. opens up a database transaction
2. reloads the record instance
3. requests exclusive access to the record
The lock is automatically released at the end of the transaction.