# validate not occurring transactionally

**URL:** <https://discuss.rubyonrails.org/t/validate-not-occurring-transactionally/37849>\
**Category:** rubyonrails-talk\
**Created:** [December 31, 2008, 9:04pm UTC](https://discuss.rubyonrails.org/t/validate-not-occurring-transactionally/37849 "2008-12-31T21:04:34Z")\
**Posts on this page:** 5\
**Page:** 1

<div class="post-metadata">

**Author:** ![Parker\_Thompson](https://avatars.discourse-cdn.com/v4/letter/p/3d9bf3/32.png) [@Parker\_Thompson](https://discuss.rubyonrails.org/u/Parker_Thompson)\
**Post date:** [December 31, 2008, 9:04pm UTC](https://discuss.rubyonrails.org/t/validate-not-occurring-transactionally/37849/1 "2008-12-31T21:04:34Z")

</div>

I'm running into a strange problem with transactions. I am seeing rare duplicate entries in the DB that should be prevented by my validations. Here is some sample code:

class Review \< ActiveRecord::Base &nbsp;&nbsp;has\_one :member &nbsp;&nbsp;has\_one :business &nbsp;&nbsp;attr\_accessor :sleep\_for

&nbsp;&nbsp;def validate &nbsp;&nbsp;&nbsp;&nbsp;validate\_uniqueness\_combo\_of\_member\_and\_business &nbsp;&nbsp;&nbsp;&nbsp;sleep(sleep\_for) if self.sleep\_for &nbsp;&nbsp;end end

and here is that code executed:

#console 1

> r = Review.new(:member\_id =\> 1, :business\_id =\> 4) r.sleep\_for = 5 r.save!

=\> true

#console 2 (during the 5 sec sleep)

> r = Review.new(:member\_id =\> 1, :business\_id =\> 4) r.save!

=\> true

I would expect the first save to lock the table and the second to fail after 5 seconds. Instead they both succeed and I end up with bad data in the db. It also fails if I wrap both save! calls in Review.transaction blocks.

I could not find docs on whether validate() is within the transaction, but I would expect it to be. Any ideas on what is wrong here?

---

<div class="post-metadata">

**Author:** ![Frederick\_Cheung](https://sea2.discourse-cdn.com/rubyonrails/user_avatar/discuss.rubyonrails.org/frederick_cheung/32/3901_2.png) [@Frederick\_Cheung](https://discuss.rubyonrails.org/u/Frederick_Cheung)\
**Post date:** [December 31, 2008, 10:58pm UTC](https://discuss.rubyonrails.org/t/validate-not-occurring-transactionally/37849/2 "2008-12-31T22:58:48Z")

</div>

Validations about uniqueness of things are prone to race conditions.  
If you need a cast iron guarantee that can only come from the database  
itself (ie unique index etc...)

---

<div class="post-metadata">

**Author:** ![Parker\_Thompson](https://avatars.discourse-cdn.com/v4/letter/p/3d9bf3/32.png) [@Parker\_Thompson](https://discuss.rubyonrails.org/u/Parker_Thompson)\
**Post date:** [December 31, 2008, 11:30pm UTC](https://discuss.rubyonrails.org/t/validate-not-occurring-transactionally/37849/3 "2008-12-31T23:30:44Z")

</div>

The app allows an "anonymous" user to have multiple reviews of a business, so a DB level constraint probably isn't appropriate. But regardless, my question is about what I can/can't expect from AR with respect to transactions/validations. If validations aren't within the transaction they're not useful. Since I don't believe the Rails folks are stupid, I'm trying to figure out what I'm doing wrong here.

Thanks,

pt.

---

<div class="post-metadata">

**Author:** ![11175](https://avatars.discourse-cdn.com/v4/letter/1/8edcca/32.png) [@11175](https://discuss.rubyonrails.org/u/11175)\
**Post date:** [January 1, 2009, 2:35am UTC](https://discuss.rubyonrails.org/t/validate-not-occurring-transactionally/37849/4 "2009-01-01T02:35:50Z")

</div>

unknown wrote:

> #console 1
> 
> > r = Review.new(:member\_id =\> 1, :business\_id =\> 4)

This line does absolutely nothing at the database level. All you have done here is make a new Ruby object.

> > r.sleep\_for = 5 r.save!

This row create a database transaction, writes the data from the Ruby object's attributes and ends the transaction.

> =\> true
> 
> #console 2 (during the 5 sec sleep)
> 
> > r = Review.new(:member\_id =\> 1, :business\_id =\> 4)

Again the database know nothing about the creation of the Ruby object.

> > r.save!

Since this executes before you have saved the first record, and validations for both records have already occurred your uniqueness validation WILL fail and allow a duplicate to be recorded in the database.

> =\> true
> 
> I would expect the first save to lock the table and the second to fail after 5 seconds. Instead they both succeed and I end up with bad data in the db. It also fails if I wrap both save! calls in Review.transaction blocks.

Locking the table in this scenario would be death to scalability. What most people do in this case is to use the validates\_uniqueness\_of for convenience, but also ensure data integrity by adding the proper unique index to the database.

A fail-and-recover scheme is what is needed here. As much as table locking sounds like the way to go, it just isn't because it's way to expensive.

I also understand your issue with anonymous users (especially since you yourself have chosen to be an anonymous coward on this forum -- kidding a little there hehe). But, that's just something you'll have to weigh for yourself.

As Frederick has already mentioned: race conditions are a tough problem to solve and are best done at the database layer. They are a lot more difficult to solve at the model object layer, which is the layer where validations are performed). This, however, does not render validations worthless. AFAIK uniqueness validations are the only ones that suffer from this issue.

---

<div class="post-metadata">

**Author:** ![Frederick\_Cheung](https://sea2.discourse-cdn.com/rubyonrails/user_avatar/discuss.rubyonrails.org/frederick_cheung/32/3901_2.png) [@Frederick\_Cheung](https://discuss.rubyonrails.org/u/Frederick_Cheung)\
**Post date:** [January 1, 2009, 9:00am UTC](https://discuss.rubyonrails.org/t/validate-not-occurring-transactionally/37849/5 "2009-01-01T09:00:23Z")

</div>

> > Validations about uniqueness of things are prone to race conditions. If you need a cast iron guarantee that can only come from the  
> > database itself (ie unique index etc...)
> 
> The app allows an "anonymous" user to have multiple reviews of a business, so a DB level constraint probably isn't appropriate. But regardless, my question is about what I can/can't expect from AR with respect to transactions/validations. If validations aren't within the transaction they're not useful. Since I don't believe the Rails folks are stupid, I'm trying to figure out what I'm doing wrong here.

It should be easy to work out whether they run in a transaction or not  
by looking at the log files. Most validations are just about the one  
object and so couldn't care less. Even if they are it wouldn't help  
your uniqueness validations (unless you're actually locking rows)

Fred
