About optimistic and pessimistic locking in Rails

Category: Ruby :: Published at: 07.06.2022

Ruby on Rails is very smart framework and it let us to handle with many things in very easy way.

There is no exception when we think about optimistic and pessimistic locking. I will show you how to handle it very fast.

OPTIMISTIC LOCKING

Optimistic locking is very useful when we want to prevent one user to overwrite changes of the second user.

Let's imagine we have blog post and two people are working on it - they both have opened edit page url and making some changes.

If first person will save the record, it will be of course updated. But if second one will click save - it will overwrite blog post and changes
made by first person will disappear forever.

Fortunately our framework has a clever solution for us.

We just need to add lock_version column which is integer:

# frozen_string_literal: true

class AddLockVersionToCars < ActiveRecord::Migration
  def change
    add_column :cars, :lock_version, :integer, null: false, default: 0
  end
end

Now - every time we save the object inside cars table - lock_version will raise by 1. 

If we save the record and someone also did it before we refreshed the page - our lock_version columns will not match. In this situation
we get StaleObject error.

Let's just remember that to make it work inside browser, we need to add the hidden field to our form:

<%= form.hidden_field :lock_version %>

After adding lock_version to the strong parameters it should work like a dream. :) 

PESSIMISTIC LOCKING

Pessimistic Locking is even easier to understand. Let's just imagine a situation when we have a EventRoom object with 100 available places for the event contestants. If we run book method as below, we will have one less place to book.

# frozen_string_literal: true

class EventRoom < ApplicationRecord
  def book!
    places =- 1
    save!
  end
end

But if we have a really huge traffic, there is a possibility that 2 people will call book! method in the same time. In this situation both people will have an object EventRoom with 100 places. After calling book! method, the code will make a calculation 100 - 1 and return 99 available places, even if 2 people will get a ticket.

To prevent it, we can use with_lock block.

# frozen_string_literal: true

class EventRoom < ApplicationRecord
  def book!
    with_lock do
      places =- 1
      save!
    end
  end
end

That's it! Right now when 2 people will have a situation like above, the code will queue both transactions and return in the end 98 available places, like it should be.

Pessimistic locking is needed especially if our transaction takes some time and one or more people can use the object with the same state and update it. This situation is called a race condition.

This method is prefered if we think that locking will be needed in a place very often. 


- Click if you liked this article

Views: 682