Forum: Ruby on Rails enforce an application constraint

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
Cathal O. (Guest)
on 2009-05-23 02:22
(Received via mailing list)
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.
Marnen L. (Guest)
on 2009-05-23 03:21
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
Roderick v. (Guest)
on 2009-05-23 14:58
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
Cathal O. (Guest)
on 2009-05-23 18:05
(Received via mailing list)
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-
Cathal O. (Guest)
on 2009-05-23 18:13
(Received via mailing list)
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-
Marnen L. (Guest)
on 2009-05-23 20:58
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
Roderick v. (Guest)
on 2009-05-23 22:24
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/As....

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
This topic is locked and can not be replied to.