I am trying to implement the Optimistic Locking for Race Condition. For that, I added an extra column lock_version
in the Product: Model through migration.
#Product: Model's new field:
# attribute_1
# lock_version :integer(4) default(0), not null
before_validation :method_1, :if => :recalculation_required_attribute
def method_1
####
####
if self.lock_version == Product.find(self.id).lock_version
Product.where(:id => self.id).update_all(attributes)
self.attributes = attributes
self.save!
end
end
Product Model has an attribute_1
. If recalculation is required for attribute_1
then before_validation: method_1
will call.
I am using optimistic locking using lock_version
. However, update_all
will not increase the lock_version
. So I start usingsave!
. Now I am getting a new error: SystemStackError: stack level too deep
because self.save!
triggers the before_validation: method1
. How to stop infinite loop of call back and handle optimistic locking in the above case.
Possible Solution:
Important Notes:
the
before_validation
above STILL DOES NOT GUARANTEE that the race condition will be avoided! because see the example below:make sure that the
before_validation
above is at the very top of your Post model, so that that callback will be triggered first before any of your other before_validations (or even any subsequent callbacks:*_update
, or*_save
), as perhaps you might have one or two subsequent callbacks that are dependent on the current state of the attributes (i.e. it's doing some calculation, or checking against some boolean-flag attribute), which then you need to reload first (as is above), before doing these calculations.the
before_validation
above will only work for "calculations/dependencies" in your model callbacks, but will not work properly if you have calculations/dependencies outside of yourProduct
model's callbacks; i.e if you have something like:The notes above are why by default Rails do not auto-reload that attributes as a
before_validation
or something, because depending on your application/business logic, you might want to "reload" or "not-reload", and this is why by default Rails instead raises anActiveRecord::StaleObjectError
(see docs) for you to specifically rescue, and handle what to do accordingly if this race-condition happened.