我有一個非常簡單的Rails應用程序,它允許用戶在一組課程中註冊他們的出席。 ActiveRecord的型號如下:如何避免我的Rails應用程序出現競態狀況?
class Course < ActiveRecord::Base
has_many :scheduled_runs
...
end
class ScheduledRun < ActiveRecord::Base
belongs_to :course
has_many :attendances
has_many :attendees, :through => :attendances
...
end
class Attendance < ActiveRecord::Base
belongs_to :user
belongs_to :scheduled_run, :counter_cache => true
...
end
class User < ActiveRecord::Base
has_many :attendances
has_many :registered_courses, :through => :attendances, :source => :scheduled_run
end
一個ScheduledRun實例具有的名額數量有限,一旦達到限制,沒有更多的上座率可以接受的。
def full?
attendances_count == capacity
end
attendances_count是一個計數器緩存列保存爲特定ScheduledRun記錄創建出席關聯的數量。
我的問題是,我不完全知道正確的方法,以確保當一個或多個人試圖在同一時間註冊課程的最後一個可用位置時不會出現競爭狀況。
我的出勤控制器看起來是這樣的:
class AttendancesController < ApplicationController
before_filter :load_scheduled_run
before_filter :load_user, :only => :create
def new
@user = User.new
end
def create
unless @user.valid?
render :action => 'new'
end
@attendance = @user.attendances.build(:scheduled_run_id => params[:scheduled_run_id])
if @attendance.save
flash[:notice] = "Successfully created attendance."
redirect_to root_url
else
render :action => 'new'
end
end
protected
def load_scheduled_run
@run = ScheduledRun.find(params[:scheduled_run_id])
end
def load_user
@user = User.create_new_or_load_existing(params[:user])
end
end
正如你所看到的,它沒有考慮到在ScheduledRun實例已達到容量帳戶。
任何幫助,將不勝感激。
更新
我不能肯定,如果這是在這種情況下,執行樂觀鎖定正確的方式,但這裏是我所做的:
我加了兩列到ScheduledRuns表 -
t.integer :attendances_count, :default => 0
t.integer :lock_version, :default => 0
我也加入到ScheduledRun模型的方法:
def attend(user)
attendance = self.attendances.build(:user_id => user.id)
attendance.save
rescue ActiveRecord::StaleObjectError
self.reload!
retry unless full?
end
保存考勤模型時,ActiveRecord繼續並更新ScheduledRun模型上的計數器緩存列。這裏的日誌輸出表示在這種情況 -
ScheduledRun Load (0.2ms) SELECT * FROM `scheduled_runs` WHERE (`scheduled_runs`.`id` = 113338481) ORDER BY date DESC
Attendance Create (0.2ms) INSERT INTO `attendances` (`created_at`, `scheduled_run_id`, `updated_at`, `user_id`) VALUES('2010-06-15 10:16:43', 113338481, '2010-06-15 10:16:43', 350162832)
ScheduledRun Update (0.2ms) UPDATE `scheduled_runs` SET `lock_version` = COALESCE(`lock_version`, 0) + 1, `attendances_count` = COALESCE(`attendances_count`, 0) + 1 WHERE (`id` = 113338481)
如果發生在ScheduledRun模型的新出席模型保存之前進行更新,這應該觸發StaleObjectError例外。如果能力尚未達到,那麼整個事情再次重新審視。
更新#2
從@ KENN的響應繼這裏是SheduledRun對象上的更新出席方法:
# creates a new attendee on a course
def attend(user)
ScheduledRun.transaction do
begin
attendance = self.attendances.build(:user_id => user.id)
self.touch # force parent object to update its lock version
attendance.save # as child object creation in hm association skips locking mechanism
rescue ActiveRecord::StaleObjectError
self.reload!
retry unless full?
end
end
end
固定在最新的導軌。 – 2012-04-10 10:10:30
您需要使用樂觀鎖定。這個截屏會告訴你如何做到這一點:[鏈接文本](http://railscasts.com/episodes/59-optimistic-locking) – rtacconi 2010-06-14 15:23:03
你是什麼意思,德米特里? – Edward 2015-06-04 01:07:44