Enforce an application constraint


#1

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.


#2

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
http://www.marnen.org
removed_email_address@domain.invalid


#3

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

On May 23, 12:21 am, Marnen Laibow-Koser <rails-mailing-l…@andreas-


#4

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.


Roderick van Domburg


#5

Cathal O’Riordan wrote:

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

No. They check anything you like – format, value, whatever. They
actually work a lot like Rails validators.

Best,

Marnen Laibow-Koser
http://www.marnen.org
removed_email_address@domain.invalid


#6

Hi Roderick,

Could you provide an example of how this would work?

regards,
C.

On May 23, 11:58 am, Roderick van Domburg <rails-mailing-l…@andreas-


#7

Cathal O’Riordan wrote:

Could you provide an example of how this would work?

Take a look at :counter_cache at
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html.

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.


Roderick van Domburg
http://www.nedforce.com