How do I avoid a race condition in my Rails app?

Posted by Cathal on Stack Overflow See other posts from Stack Overflow or by Cathal
Published on 2010-06-14T12:03:11Z Indexed on 2010/06/14 12:32 UTC
Read the original article Hit count: 204

Hi,

I have a really simple Rails application that allows users to register their attendance on a set of courses. The ActiveRecord models are as follows:

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

A ScheduledRun instance has a finite number of places available, and once the limit is reached, no more attendances can be accepted.

def full?
  attendances_count == capacity
end

attendances_count is a counter cache column holding the number of attendance associations created for a particular ScheduledRun record.

My problem is that I don't fully know the correct way to ensure that a race condition doesn't occur when 1 or more people attempt to register for the last available place on a course at the same time.

My Attendance controller looks like this:

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

As you can see, it doesn't take into account where the ScheduledRun instance has already reached capacity.

Any help on this would be greatly appreciated.

© Stack Overflow or respective owner

Related posts about ruby-on-rails

Related posts about database