Ho un'applicazione Rails davvero semplice che consente agli utenti di registrare la loro presenza su una serie di corsi. I modelli ActiveRecord sono i seguenti:Come evitare una condizione di competizione nella mia app 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
esempio Uno ScheduledRun ha un numero finito di posti disponibili, e una volta raggiunto il limite, non più presenze può essere accolto.
def full?
attendances_count == capacity
end
attendances_count è una colonna di cache contatore che tiene il numero di associazioni di presenza creati per un record ScheduledRun.
Il mio problema è che non conosco appieno il modo corretto per garantire che una condizione di competizione non si verifichi quando 1 o più persone tentano di registrarsi per l'ultimo posto disponibile su un campo allo stesso tempo.
mio regolatore di frequenza simile a questa:
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
Come si può vedere, non tiene conto in cui l'istanza ScheduledRun ha già raggiunto la capacità.
Qualsiasi aiuto su questo sarebbe molto apprezzato.
Aggiornamento
Io non sono certo se questo è il modo giusto per eseguire il blocco ottimistico in questo caso, ma qui è quello che ho fatto:
ho aggiunto due colonne alla tabella ScheduledRuns -
t.integer :attendances_count, :default => 0
t.integer :lock_version, :default => 0
anche ho aggiunto un metodo per il modello ScheduledRun:
def attend(user)
attendance = self.attendances.build(:user_id => user.id)
attendance.save
rescue ActiveRecord::StaleObjectError
self.reload!
retry unless full?
end
Quando il modello di presenze viene salvato, ActiveRecord procede e aggiorna la colonna della cache del contatore sul modello ScheduledRun. Ecco la proiezione di uscita registro in cui questo accade -
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)
Se un aggiornamento successivo si verifica al modello ScheduledRun prima che il nuovo modello di partecipazione è salvato, questo dovrebbe far scattare l'eccezione StaleObjectError. A quel punto, l'intera operazione viene ritentata di nuovo, se la capacità non è già stata raggiunta.
Aggiornamento # 2
Sulla scia @ risposta di Kenn Ecco il metodo frequentare aggiornato sull'oggetto 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
Risolto negli ultimi binari. –
È necessario utilizzare il blocco ottimistico. Questo screencast ti mostrerà come farlo: [link text] (http://railscasts.com/episodes/59-optimistic-locking) – rtacconi
Cosa intendi con dmitry? – Edward