“我的出勤”模型是“用户”与“事件”之间的关联,用于记录用户对给定事件的出勤。任何事件的可用座位数都会随着用户每次为该事件注册而减少。以下是“出勤”控制器的内容:
before_action :logged_in_user
before_action :participants_limit, only: :create
def create
@attendance = current_user.attendances.build(attendance_params)
if @attendance.save
@event.decrement!(:seats)
flash[:success] = "Congratulation! Your participation has been recorded."
redirect_back(fallback_location: root_url)
else
flash[:danger] = @attendance.errors.full_messages.join(', ')
redirect_back(fallback_location: root_url)
end
end
private
def attendance_params
params.require(:attendance).permit(:event_id)
end
def participants_limit
@event = Event.find(params[:attendance][:event_id])
if @event.seats == 0
flash[:danger] = 'Attention! Unfortunately this event is full!'
redirect_back(fallback_location: root_url)
end
end
在previous post中,Vasfed警告我说,如果两个参与者尝试同时注册,则我的代码容易受到竞争条件的影响。因此,我试图弄清楚如何防止出现种族状况。最糟糕的情况可能是两个用户尝试注册最后一个可用座位,即@event.seats == 1
。在这种情况下,两个用户都将在操作之前通过participants_limit
并进入创建操作。我不知道此时使用with_lock
是否可行或无用:
def create
@attendance = current_user.attendances.build(attendance_params)
if @attendance.save
@event.with_lock do
@event.decrement(:seats)
flash[:success] = "Congratulation! Your participation has been recorded."
redirect_back(fallback_location: root_url)
end
else
flash[:danger] = @attendance.errors.full_messages.join(', ')
redirect_back(fallback_location: root_url)
end
end
我唯一想到的替代方法是摆脱before_action并将其代码合并到create动作中,如下所示:
def create
@attendance = current_user.attendances.build(attendance_params)
@event = Event.find(params[:attendance][:event_id])
@event.with_lock do
if @event.seats == 0
flash[:danger] = 'Attention! Unfortunately this event is full!'
redirect_back(fallback_location: root_url)
elsif @attendance.save
@event.decrement(:seats)
flash[:success] = "Congratulation! Your participation has been recorded."
redirect_back(fallback_location: root_url)
else
flash[:danger] = @attendance.errors.full_messages.join(', ')
redirect_back(fallback_location: root_url)
end
end
end
这是我第一次遇到此问题,因此我没有悲观锁定的经验,我将不胜感激。