Ruby on rails 如何在基于父属性创建子id时避免竞争条件
想象一下,你有一个Ruby on rails 如何在基于父属性创建子id时避免竞争条件,ruby-on-rails,ruby,Ruby On Rails,Ruby,想象一下,你有一个网站,它有一个“接受数字”的东西,人们接受一个数字,然后等待轮到他们。假设每个数字都是一个顺序。这种关系是一个站点有许多订单 在一个给定的重置点,站点的管理者将使用新的数字卷替换“获取数字”内容,也就是说,你可以想象这种情况每天都在发生。然而,“拿一个数字”的东西是用有限的数字制造的,所以每卷由,比如说1-100组成,然后下一卷又从100-999开始 我试图对上述行为进行建模,以及我是如何看待它的: 在站点父站点上,有一个开始编号属性。点击重置/交换卷,将开始编号重置为100,
网站
,它有一个“接受数字”的东西,人们接受一个数字,然后等待轮到他们。假设每个数字都是一个顺序
。这种关系是一个站点
有许多订单
在一个给定的重置点,站点的管理者将使用新的数字卷替换“获取数字”内容,也就是说,你可以想象这种情况每天都在发生。然而,“拿一个数字”的东西是用有限的数字制造的,所以每卷由,比如说1-100组成,然后下一卷又从100-999开始
我试图对上述行为进行建模,以及我是如何看待它的:
站点
父站点上,有一个开始编号
属性。点击重置/交换卷,将开始编号
重置为100,即卷的第一个编号Order
子级上,有一个分配数字的回调。如果父站点
编号为100,则这意味着这是自第1步中所述重置以来的第一个订单
,因此该编号也为100。现在,父站点
将自动更新,使其不再处于重置状态(例如,开始编号
不再为100)。对于未来的订单
,分配的编号仅为上一订单后的下一个编号class Site
has_many :orders
end
class Order
belongs_to :site
before_save :assign_number
def assign_number
if site.start_number == 100
self.number = 100
self.site.update_column(:start_number, nil)
else
self.number = self.site.orders.where.not(number:nil).last.number + 1
end
end
end
但这是糟糕的,因为与现实世界中的“拿一个数字”不同,2个订单可以同时处理,对Order.number
没有unique
约束,因为数字可以重复使用(滚动会被重置)。但是,如果在重置时,两个放在一起的订单都是100
,则显然没有帮助。如果确实发生了重置事件,而不仅仅是巧合的时间,您只希望多个订单共享一个编号
这种方法的另一个问题是,一个订单后面紧跟着第二个订单。例如,假设最后分配的编号是415
,两个订单快速连续。第一个被分配到416
,第二个如此接近,以至于self.site.orders.where.not(number:nil)。最后一个.number
仍然返回415
(即,416
尚未保存),因此第二个订单现在也被分配到416
如果能获得关于如何更好地模拟所需行为的想法,那就太好了。谢谢
更新根据@Fernando的评论,我将使用一个悲观锁,我将按照notes实现它。所以现在代码看起来像:
def assign_number
site = self.site.lock!
if site.start_number == 100
self.number = 100
site.start_number = nil
else
last_order = self.site.orders.where.not(number:nil).last.lock!
self.number = last_order.number + 1
last_order.save! # releases lock
end
site.save! #releases lock, whether or not call number was updated to nil
end
我不完全确定如何规范这虽然。。。由于编写规范是按定义排序的。。。如何强制两个订单紧密保存以模拟此行为?一种方法是创建另一个表来存储最新的编号
#code :string not null
#number :bigint default(0), not null
class Serial < ApplicationRecord
def self.get_latest_number
Serial.transaction do
self.lock.find_or_create_by!(code: 'just_any_identification').increment!(:number).count
end
end
end
请注意。一种方法是创建另一个存储最新编号的表
#code :string not null
#number :bigint default(0), not null
class Serial < ApplicationRecord
def self.get_latest_number
Serial.transaction do
self.lock.find_or_create_by!(code: 'just_any_identification').increment!(:number).count
end
end
end
请注意。谢谢!更新了我的问题,我现在正在使用锁,更喜欢那个评级器而不是一个新表。关于如何测试这个问题有什么想法吗?我不知道如何测试这种情况。可能接受\u嵌套的\u属性\u?示例:Site.new({:order\u attributes=>{…})。您可能应该发布一个关于测试的新问题,以吸引其他观众。谢谢!更新了我的问题,我现在正在使用锁,更喜欢那个评级器而不是一个新表。关于如何测试这个问题有什么想法吗?我不知道如何测试这种情况。可能接受\u嵌套的\u属性\u?示例:Site.new({:order\u attributes=>{…})。您可能应该发布一个关于测试的新问题,以吸引其他观众。