У меня действительно простое приложение 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 записи.
Моя проблема заключается в том, что я не полностью знаю правильный способ гарантировать, что условие гонки не произойдет, если 1 или более человек попытаются зарегистрироваться для последнего доступного места на курсе одновременно.
Мой контроллер посещаемости выглядит следующим образом:
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 уже достиг мощности.
Любая помощь в этом была бы принята с благодарностью.
Update
Я не уверен, если это правильный путь, чтобы выполнить оптимистическую блокировку в этом случае, но вот что я сделал:
Я добавил два столбца таблицы 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. В этот момент все это снова повторяется, если пропускная способность еще не достигнута.
Update # 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
Исправлено в последних рельсах. –
Вам нужно использовать оптимистичную блокировку. Этот скринкаст покажет вам, как это сделать: [link text] (http://railscasts.com/episodes/59-optimistic-locking) – rtacconi
Что вы имеете в виду, dmitry? – Edward