enforce an application constraint

Hi,

In my app. I have the following set of models

class Course < ActiveRecord::Base   has_many :registrations   has_many :users, :through => :registrations end

class User < ActiveRecord::Base   has_many :registrations   has_many :courses, :through => :registrations end

class Registration < ActiveRecord::Base   belongs_to :user   belongs_to :course end

each course model has a 'capacity' attribute, which dictates how many places are available on that particular course. This is how I enforce the 'capacity' constraint when a registration is created:

class Course < ActiveRecord::Base ...   def enrol(user)     if registration_count >= capacity       errors.add_to_base("this course is now full")       return false     end     self.registrations.create(:user_id => user.id)   end

  def registration_count     registrations.length   end ... end

My concern is that my code won't guard against a race condition where 2 or more users try to register for a course that is near it capacity limit. My first thought was to wrap the registration code in a transaction and rollback if the capacity of registrations on a course has been exceeded.

I'm not sure if this is a valid approach and I would love to hear from someone who could provide some adivice on how I might proceed.

thanks and regards, C.

Cathal O'Riordan wrote: [...]

My concern is that my code won't guard against a race condition where 2 or more users try to register for a course that is near it capacity limit. My first thought was to wrap the registration code in a transaction and rollback if the capacity of registrations on a course has been exceeded.

[...]

Use check constraints in the DB and let *it*, not your app, worry about concurrency issues!

If your DB server can't handle check constraints, get one that can.

Best,

Marnen Laibow-Koser wrote:

Cathal O'Riordan wrote: [...]

My concern is that my code won't guard against a race condition where 2 or more users try to register for a course that is near it capacity limit. My first thought was to wrap the registration code in a transaction and rollback if the capacity of registrations on a course has been exceeded.

[...]

Use check constraints in the DB and let *it*, not your app, worry about concurrency issues!

That's one approach. Wrapping it up in a transaction with read isolation would work fine too. Alternatively (and better for concurrency), keep a counter cache on the registration_count and use the default isolation level.

Don't check constrainsts just ensure that data is a certain format?

Hi Roderick,

Could you provide an example of how this would work?

regards, C.

Cathal O'Riordan wrote:

Could you provide an example of how this would work?

Take a look at :counter_cache at ActiveRecord::Associations::ClassMethods.

Wrap everything up in a transaction, fetch the registration count, administer the new registration and update the counter cache. Any concurrent registrations will be rolled back by the database -- don't forget to catch that exception and deal with it.