Let's say we have two users and we want to create a money transfer between these users, but we have to make sure that if there is an error in any of the steps, the execution will be canceled and other transactions will only be executed when the current transaction ends, this is also known as Race Condition.
To solve this problem or similar problems, there is a technique in Rails called Pessimistic Lock or Pessimistic Strategy, it provides a row-level locking in SQL like SELECT … FOR UPDATE
and others and it uses ActiveRecord Transactions. Let's take a look at this code:
def transfer_money_from_user_to_user(user1, user2)
# Charge 200 from the user1...
user1.balance(-=200)
user1.save! # Callbacks and other things...
# and pay the user2
user2.balance(+=200)
user2.save! # Callbacks and other things...
end
If for some reason, the first transaction (charge from user1) fails, it will not prevent the second one to be executed. We have two ways to prevent this using Rails, lock! and with_lock, let's take a look:
Lock!
Using Transactions and lock! we could do something like:
def transfer_money_from_user_to_user(user1, user2)
# Initialize an ActiveRecord Transaction
ActiveRecord::Base.transaction do
# Lock the users
user1.lock!
user2.lock!
# Charge 200 from the user1...
user1.balance(-=200)
user1.save!
# Pay the user2
user2.balance(+=200)
user2.save!
end
end
With_lock
With_lock simplifies our code and creates a transaction wrapper:
def transfer_money_from_user_to_user(user1, user2)
user1.with_lock do
# Charge 200 from the user1...
user1.balance(-=200)
user1.save!
# Pay the user2
user2.balance(+=200)
user.save!
end
end
That's it. Using Pessimistic Locking inside Rails is a good way to prevent these kind of failures. See you!
Top comments (0)