我正在尝试对比赛条件实施乐观锁定。为此,我在“产品:通过迁移进行建模”中添加了额外的列lock_version
。
#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
产品模型具有attribute_1
。如果attribute_1
需要重新计算,则会调用before_validation: method_1
。
我正在使用lock_version
进行乐观锁定。但是,update_all
不会增加lock_version
。因此,我开始使用save!
。现在,我收到一个新错误:SystemStackError: stack level too deep
,因为self.save!
触发了before_validation: method1
。在上述情况下,如何停止回叫的无限循环并处理乐观锁定。
答案 0 :(得分:0)
class Product < ApplicationRecord
before_validation :reload_and_apply_changes_if_stale, on: :update
def reload_and_assign_changes_if_stale
# if stale
if lock_version != Post.find(id).lock_version
# store the "changes" first into a backup variable
current_changes = changes
# reload this record from "real" up-to-date values from DB (because we already know that it's stale)
reload
# after reloading, `changes` now becomes `{}`, and is why we need the backup variable `current_changes` above
# now finally, assign back again all the "changed" values
current_changes.each do |attribute_name, change|
change_from = change[0] # you can remove this line
change_to = change[1]
self[attribute_name] = change_to
end
end
end
end
上面的before_validation
仍然不能保证避免出现竞争情况!因为请参见下面的示例:
class Product < ApplicationRecord
# this triggers first...
before_validation :reload_and_apply_changes_if_stale, on: :update
# then, this triggers next...
before_update :do_some_heavy_loooong_calculation
def do_some_heavy_loooong_calculation
sleep(60.seconds)
# ... of which during this time, this record might already be stale! as perhaps another "process" or another "server" has already updated this record!
end
确保上面的before_validation
在您的Post模型的顶部,这样该回调将首先在您的其他before_validations(甚至任何后续回调:{{1 }}或*_update
),因为您可能有一个或两个后续的回调,这些回调取决于属性的当前状态(即,它正在执行一些计算或检查某些boolean-flag属性),然后您需要先重新加载(如上所述),然后再执行这些计算。
上面的*_save
仅适用于模型回调中的“计算/依赖关系”,但如果您的模型before_validation
回调之外具有计算/依赖关系,则无法正常工作;也就是说,如果您有类似的东西:
Product
上面的注释是为什么默认情况下,Rails不会自动重载该属性为class ProductsController < ApplicationController
def update
@product = Product.find(params[:id])
# let's assume at this line, @product.cost = nil (no value yet)
@product.assign_attributes(product_attributes)
# let's assume at this line, @product.cost = 1100
# because 1100 > 1000, then DO SOME IMPORTANT THING!
if @product.cost_was.nil? && @product.cost > 1_000.0
# do some important thing!
end
# however, when `product.save` is called below and the `before_validation :reload_and_apply_changes_if_stale` is triggered,
# of which let's say some other "process" has already updated this
# exact same record, and thus @product is reloaded, but the real DB value is now
# @product.cost = 900; there's no WAY TO UNDO SOME IMPORTANT THING! above
@product.save
end
end
之类的原因,因为根据您的应用程序/业务逻辑,您可能想“重载”或“不重载” -reload”,这就是为什么默认情况下,Rails会引发before_validation
(see docs)以便您专门营救,并处理发生这种竞争情况时应采取的相应措施。