Tengo una aplicación de Rails realmente simple que permite a los usuarios registrar su asistencia en un conjunto de cursos. Los modelos ActiveRecord son los siguientes:¿Cómo evito una condición de carrera en mi aplicación 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
ejemplo A ScheduledRun tiene un número finito de lugares disponibles, y una vez que se alcanza el límite, no más asistencias puede ser aceptada.
def full?
attendances_count == capacity
end
attendances_count es una columna de caché contador que tiene el número de asociaciones de asistencia creadas para un registro ScheduledRun particular.
Mi problema es que no conozco completamente la forma correcta de asegurar que una condición de carrera no ocurra cuando 1 o más personas intentan registrarse para el último lugar disponible en un curso al mismo tiempo.
Mi controlador de asistencia es el siguiente:
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
Como se puede ver, no tiene en cuenta en la instancia ScheduledRun ya ha alcanzado su capacidad máxima.
Cualquier ayuda sobre esto sería muy apreciada.
actualización
No estoy seguro de si este es el camino correcto para llevar a cabo el bloqueo optimista en este caso, pero esto es lo que hice:
añadí dos columnas a la tabla ScheduledRuns -
t.integer :attendances_count, :default => 0
t.integer :lock_version, :default => 0
I también se ha añadido un método para modelo ScheduledRun:
def attend(user)
attendance = self.attendances.build(:user_id => user.id)
attendance.save
rescue ActiveRecord::StaleObjectError
self.reload!
retry unless full?
end
Cuando se guarda el modelo de Asistencia, ActiveRecord continúa y actualiza la columna de la memoria caché del contador en el modelo ScheduledRun. Aquí está la muestra de salida de registro donde esto sucede -
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)
Si se produce una actualización posterior al modelo ScheduledRun antes de guardar el nuevo modelo de asistencia, esto debe dar lugar a la excepción StaleObjectError. En ese punto, todo vuelve a intentarse, si todavía no se ha alcanzado la capacidad.
Actualización # 2
raíz de @ respuesta de Kenn aquí está el método asistir actualizada sobre el objeto 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
Fijo en los últimos rieles. –
Necesita usar un bloqueo optimista. Este screencast le mostrará cómo hacerlo: [link text] (http://railscasts.com/episodes/59-optimistic-locking) – rtacconi
¿Qué quiere decir, dmitry? – Edward