Tôi có một ứng dụng Rails thực sự đơn giản cho phép người dùng đăng ký tham dự của họ trên một tập hợp các khóa học. Các mô hình ActiveRecord như sau:Làm cách nào để tránh tình trạng cuộc đua trong ứng dụng Rails của tôi?
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
Ví dụ ScheduledRun có số lượng hữu hạn các địa điểm có sẵn, và khi đạt đến giới hạn, không thể chấp nhận thêm.
def full?
attendances_count == capacity
end
Attances_count là cột bộ nhớ cache truy cập giữ số lượng liên kết tham dự được tạo cho bản ghi ScheduledRun cụ thể.
Vấn đề của tôi là tôi không hoàn toàn biết đúng cách để đảm bảo rằng điều kiện chủng tộc không xảy ra khi 1 hoặc nhiều người cố đăng ký địa điểm cuối cùng có sẵn trên một khóa học cùng một lúc.
điều khiển tham dự của tôi trông như thế này:
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
Như bạn thấy, nó không đưa vào tài khoản mà dụ ScheduledRun đã đạt công suất.
Bất kỳ trợ giúp nào về điều này sẽ được đánh giá cao.
Cập nhật
Tôi không chắc chắn nếu điều này là đúng cách để thực hiện khóa lạc quan trong trường hợp này, nhưng đây là những gì tôi đã làm:
tôi bổ sung thêm hai cột vào bảng ScheduledRuns -
t.integer :attendances_count, :default => 0
t.integer :lock_version, :default => 0
tôi cũng đã thêm một phương pháp để mô hình ScheduledRun:
def attend(user)
attendance = self.attendances.build(:user_id => user.id)
attendance.save
rescue ActiveRecord::StaleObjectError
self.reload!
retry unless full?
end
Khi mô hình tham dự được lưu, ActiveRecord tiếp tục và cập nhật cột bộ nhớ cache truy cập trên mô hình ScheduledRun. Đây là hiển thị dữ liệu ghi nhận nơi này xảy ra -
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)
Nếu một bản cập nhật tiếp theo xảy ra với mô hình ScheduledRun trước khi mô hình tham dự mới được lưu, điều này sẽ kích hoạt các ngoại lệ StaleObjectError. Tại thời điểm đó, toàn bộ điều được thử lại, nếu năng lực chưa đạt được.
Update # 2
Tiếp theo từ @ phản ứng Kenn ở đây là phương pháp tham dự được cập nhật trên các đối tượng 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
Cố định trong đường ray mới nhất. –
Bạn cần sử dụng khóa lạc quan. Screencast này sẽ chỉ cho bạn cách thực hiện: [link text] (http://railscasts.com/episodes/59-optimistic-locking) – rtacconi
Ý bạn là gì, dmitry? – Edward